ЭНКОДЕР В MICROCHIP STUDIO

encoder 1

Вот с этой программой я здорово наебался. Пробовал разные варианты, в том числе всякие алгоритмы на основе Гайверовских, и чат ГПТ никак не мог помочь. Все его поделки допускали прокруты, "холостой ход", то есть, при повороте крутилки происходит характерный щелчок - в UART подается сообщение, свидетельствующее о том, что движение зарегистрированно, однако порядок цифр каждый раз спонтанный, четкой последовательности нет. Я уже начал думать в какой-то момент, что у меня хреновый энкодер... Хотя, если смотреть по осциллографу, то вроде бы нормальный рисунок... Все красивенько, там по обоим каналами идут ровненькие меандры и один непременно, как и положено, отстает от другого. И вот, в какой-то момент мне пришла в голову использовать тот же энкодер с каким-нибудь готовым Ардуино-кодом вообще на другом микроконтроллере. Нашел в Инете какой-то говнопример, залил его в ESP32... И оно заработало! Можно было бы на этом остановиться, но я ведь не гомик и не могу пользоваться гейским Ардуино-кодом, поэтому пришлось еще немного попариться, чтобы переписать его на "православной" Сишечке, адаптировав под Microchip Studio. Взял исходную библиотеку, объединил ее в один файл, переписал под AVR, добавил логику управления, и вот он, итоговый шаблон:

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// Настройки UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// Define encoder pins
#define ENCODER_PIN1 PB1
#define ENCODER_PIN2 PB2
#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3
// Rotary encoder variables
volatile int8_t oldState;
volatile long position;
volatile long positionExt;
volatile long positionExtPrev;
unsigned long positionExtTime;
unsigned long positionExtTimePrev;
// Global millisecond counter
volatile unsigned long milliseconds = 0;
const int8_t KNOBDIR[] = {
    0, -1, 1, 0,
    1, 0, 0, -1,
    -1, 0, 0, 1,
    0, 1, -1, 0
};
// Преобразование числа в строку
char* itoa(int value, char* buffer, int base) {
    if (base < 2 || base > 36) {
        *buffer = '\0';
        return buffer;
    }
    char* ptr = buffer, *ptr1 = buffer, tmp_char;
    int tmp_value;
    if (value < 0 && base == 10) {
        value = -value;
        *ptr++ = '-';
    }
    tmp_value = value;
    do {
        int remainder = tmp_value % base;
        *ptr++ = (remainder < 10) ? (remainder + '0') : (remainder - 10 + 'a');
    } while (tmp_value /= base);
    if (*buffer == '-') {
        ptr1++;
    }
    *ptr-- = '\0';
    while (ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr-- = *ptr1;
        *ptr1++ = tmp_char;
    }
    return buffer;
}
// Функция для инициализации UART
void UART_init(unsigned int ubrr) {
    // Устанавливаем скорость передачи
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;
    // Разрешаем передачу
    UCSR0B = (1 << TXEN0);
    // Устанавливаем формат кадра: 8 бит данных, 1 стоп-бит
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Функция для передачи одного байта данных
void UART_transmit(unsigned char data) {
    // Ожидаем, пока освободится буфер передачи
    while (!(UCSR0A & (1 << UDRE0)));
    // Помещаем данные в буфер
    UDR0 = data;
}
// Функция для передачи строки
void UART_putstring(const char *s) {
    while (*s) {
        UART_transmit(*s++);
    }
}
// Millis function
unsigned long millis() {
    unsigned long ms;
    // Disable interrupts temporarily to prevent variable corruption
    cli();
    ms = milliseconds;
    sei();
    return ms;
}
// Initialize rotary encoder
void rotary_encoder_init() {
    // Set encoder pins as inputs with pull-ups
    DDRB &= ~(1 << ENCODER_PIN1); // Set PB1 as input
    DDRB &= ~(1 << ENCODER_PIN2); // Set PB2 as input
    PORTB |= (1 << ENCODER_PIN1); // Enable pull-up on PB1
    PORTB |= (1 << ENCODER_PIN2); // Enable pull-up on PB2
    // Initialize state
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    oldState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    // Initialize position
    position = 0;
    positionExt = 0;
    positionExtPrev = 0;
    positionExtTime = 0;
    positionExtTimePrev = 0;
}
// Read the encoder and update position
void rotary_encoder_tick() {
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    int8_t thisState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    if (oldState != thisState) {
        position += KNOBDIR[thisState | (oldState << 2)];
        oldState = thisState;
        if (thisState == LATCH0 || thisState == LATCH3) {
            positionExt = position >> 2;
            positionExtTimePrev = positionExtTime;
            positionExtTime = millis();
        }
    }
}
// Get the current position
long rotary_encoder_get_position() {
    return positionExt;
}
// Get time between rotations
unsigned long rotary_encoder_get_millis_between_rotations() {
    return (positionExtTime - positionExtTimePrev);
}
// Get RPM
unsigned long rotary_encoder_get_rpm() {
    unsigned long timeBetweenLastPositions = positionExtTime - positionExtTimePrev;
    unsigned long timeToLastPosition = millis() - positionExtTime;
    unsigned long t = (timeBetweenLastPositions > timeToLastPosition) ? timeBetweenLastPositions : timeToLastPosition;
    return 60000UL / (t * 20);
}
void setup(){
    UART_init(MYUBRR); // Инициализируем UART
    // Initialize encoder and timer
    rotary_encoder_init();
    // Set up Timer0 for millis()
    TCCR0A = (1 << WGM01); // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler 64
    OCR0A = 249; // Set compare value for 1ms interrupt at 16MHz
    TIMSK0 = (1 << OCIE0A); // Enable Timer0 compare interrupt
    sei(); // Enable global interrupts
}
void loop(){
    long pos = 0;
    long newPos;
    char buffer[16]; // Буфер для хранения строки
    while (1) {
        rotary_encoder_tick();
        newPos = rotary_encoder_get_position();
        if (pos != newPos) {
            // Преобразование newPos в строку и вывод через UART
            itoa(newPos, buffer, 10);
            UART_putstring(buffer);
            UART_putstring("\n");
            pos = newPos;
        }
    }
}
// Main function
int main(void) {
    setup();
    loop();
}
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds++;
}
 

По-моему, здесь все прекрасно. Тут даже есть свой собственный millis() с блэкджеком и шлюхами! Красота! )

А вот еще версия на внешних прерываниях. Кстати, я так и не смог добиться от известного пиздабол-чата, чтобы он мне сделал что-то подобное иcходя из примера выше. Пришлось переписывать вручную:

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// Настройки UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// Define encoder pins
#define ENCODER_PIN1 PB1
#define ENCODER_PIN2 PB2
#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3
long pos = 0;
long newPos;
char buffer[16]; // Буфер для хранения строки
// Rotary encoder variables
volatile int8_t oldState;
volatile long position;
volatile long positionExt;
volatile long positionExtPrev;
unsigned long positionExtTime;
unsigned long positionExtTimePrev;
// Global millisecond counter
volatile unsigned long milliseconds = 0;
const int8_t KNOBDIR[] = {
    0, -1, 1, 0,
    1, 0, 0, -1,
    -1, 0, 0, 1,
    0, 1, -1, 0
};
// Преобразование числа в строку
char* itoa(int value, char* buffer, int base) {
    if (base < 2 || base > 36) {
        *buffer = '\0';
        return buffer;
    }
    char* ptr = buffer, *ptr1 = buffer, tmp_char;
    int tmp_value;
    if (value < 0 && base == 10) {
        value = -value;
        *ptr++ = '-';
    }
    tmp_value = value;
    do {
        int remainder = tmp_value % base;
        *ptr++ = (remainder < 10) ? (remainder + '0') : (remainder - 10 + 'a');
    } while (tmp_value /= base);
    if (*buffer == '-') {
        ptr1++;
    }
    *ptr-- = '\0';
    while (ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr-- = *ptr1;
        *ptr1++ = tmp_char;
    }
    return buffer;
}
// Функция для инициализации UART
void UART_init(unsigned int ubrr) {
    // Устанавливаем скорость передачи
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;
    // Разрешаем передачу
    UCSR0B = (1 << TXEN0);
    // Устанавливаем формат кадра: 8 бит данных, 1 стоп-бит
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Функция для передачи одного байта данных
void UART_transmit(unsigned char data) {
    // Ожидаем, пока освободится буфер передачи
    while (!(UCSR0A & (1 << UDRE0)));
    // Помещаем данные в буфер
    UDR0 = data;
}
// Функция для передачи строки
void UART_putstring(const char *s) {
    while (*s) {
        UART_transmit(*s++);
    }
}
// Millis function
unsigned long millis() {
    unsigned long ms;
    // Disable interrupts temporarily to prevent variable corruption
    cli();
    ms = milliseconds;
    sei();
    return ms;
}
// Initialize rotary encoder
void rotary_encoder_init() {
    // Set encoder pins as inputs with pull-ups
    DDRB &= ~(1 << ENCODER_PIN1); // Set PB1 as input
    DDRB &= ~(1 << ENCODER_PIN2); // Set PB2 as input
    PORTB |= (1 << ENCODER_PIN1); // Enable pull-up on PB1
    PORTB |= (1 << ENCODER_PIN2); // Enable pull-up on PB2
    //Настройка прерываний
    // Включаем прерывания на порту B
    PCICR |= (1 << PCIE0); // Enable PCINT0 (Port B)
    PCMSK0 |= (1 << PCINT1) | (1 << PCINT2);   // Включаем прерывания для конкретных пинов PB1 и PB2    
    // Initialize state
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    oldState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    // Initialize position
    position = 0;
    positionExt = 0;
    positionExtPrev = 0;
    positionExtTime = 0;
    positionExtTimePrev = 0;
}
// Read the encoder and update position
void rotary_encoder_tick() {
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    int8_t thisState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    if (oldState != thisState) {
        position += KNOBDIR[thisState | (oldState << 2)];
        oldState = thisState;
        if (thisState == LATCH0 || thisState == LATCH3) {
            positionExt = position >> 2;
            positionExtTimePrev = positionExtTime;
            positionExtTime = millis();
        }
    }
}
// Get the current position
long rotary_encoder_get_position() {
    return positionExt;
}
// Get time between rotations
unsigned long rotary_encoder_get_millis_between_rotations() {
    return (positionExtTime - positionExtTimePrev);
}
// Get RPM
unsigned long rotary_encoder_get_rpm() {
    unsigned long timeBetweenLastPositions = positionExtTime - positionExtTimePrev;
    unsigned long timeToLastPosition = millis() - positionExtTime;
    unsigned long t = (timeBetweenLastPositions > timeToLastPosition) ? timeBetweenLastPositions : timeToLastPosition;
    return 60000UL / (t * 20);
}
void setup(){
    UART_init(MYUBRR); // Инициализируем UART
    // Initialize encoder and timer
    rotary_encoder_init();
    // Set up Timer0 for millis()
    TCCR0A = (1 << WGM01); // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler 64
    OCR0A = 249; // Set compare value for 1ms interrupt at 16MHz
    TIMSK0 = (1 << OCIE0A); // Enable Timer0 compare interrupt
    sei(); // Enable global interrupts
}
void EN(){
    rotary_encoder_tick();
    newPos = rotary_encoder_get_position();
    if (pos != newPos) {
        // Преобразование newPos в строку и вывод через UART
        itoa(newPos, buffer, 10);
        UART_putstring(buffer);
        UART_putstring("\n");
        pos = newPos;
    }
}
void loop(){
 
    while (1) {
 
    }
}
// Main function
int main(void) {
    setup();
    loop();
}
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds++;
}
// Обработчик прерывания PCINT
ISR(PCINT0_vect) {
    EN();
}

Кстати, я тут запарился с тем, чтобы сделать ускоритель проворотов, чтобы пользователь крутилки мог быстрее достичь целевого значения и тут пришлось изучить код более подробно в поисках места, куда бы вставить множитель, работающий по таймеру и счетчику тиков. Конечно, я использую вот эту вторую версию обработчика энкодера, на прерываниях. Прерывания здесь срабатывают 2 раза, а тиков оказалось 4 за один поворот. Вставлять какие-то сложные конструкции в конструкторы, отвечающие за обработку положения энкодера, оказалось чревато сбоями. Единственный вариант, куда можно относительно безболезненно вставить множитель, ускоряющий отчет - это массив KNOBDIR[]. Например, здесь вместо единичек можно поставить десятки:

volatile int Mu = 10; // Глобальная переменная для множителя
int8_t KNOBDIR[16];  // Массив для значений
void updateKnobDir() {
    KNOBDIR[0] = 0;
    KNOBDIR[1] = -Mu;
    KNOBDIR[2] = Mu;
    KNOBDIR[3] = 0;
    KNOBDIR[4] = Mu;
    KNOBDIR[5] = 0;
    KNOBDIR[6] = 0;
    KNOBDIR[7] = -Mu;
    KNOBDIR[8] = -Mu;
    KNOBDIR[9] = 0;
    KNOBDIR[10] = 0;
    KNOBDIR[11] = Mu;
    KNOBDIR[12] = 0;
    KNOBDIR[13] = Mu;
    KNOBDIR[14] = -Mu;
    KNOBDIR[15] = 0;
}

 А вот и ускоряемая крутилка с ограничением отчета от 0 до 9999:

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// Настройки UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// Define encoder pins
#define ENCODER_PIN1 PB1
#define ENCODER_PIN2 PB2
#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3
// Rotary encoder variables
volatile int8_t oldState;
volatile long position;
volatile long positionExt;
volatile long positionExtPrev;
unsigned long positionExtTime;
unsigned long positionExtTimePrev;
// Global millisecond counter
volatile unsigned long milliseconds = 0;
const int8_t KNOBDIR[] = {
    0, -1, 1, 0,
    1, 0, 0, -1,
    -1, 0, 0, 1,
    0, 1, -1, 0
};
// Преобразование числа в строку
char* itoa(int value, char* buffer, int base) {
    if (base < 2 || base > 36) {
        *buffer = '\0';
        return buffer;
    }
    char* ptr = buffer, *ptr1 = buffer, tmp_char;
    int tmp_value;
    if (value < 0 && base == 10) {
        value = -value;
        *ptr++ = '-';
    }
    tmp_value = value;
    do {
        int remainder = tmp_value % base;
        *ptr++ = (remainder < 10) ? (remainder + '0') : (remainder - 10 + 'a');
    } while (tmp_value /= base);
    if (*buffer == '-') {
        ptr1++;
    }
    *ptr-- = '\0';
    while (ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr-- = *ptr1;
        *ptr1++ = tmp_char;
    }
    return buffer;
}
// Функция для инициализации UART
void UART_init(unsigned int ubrr) {
    // Устанавливаем скорость передачи
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;
    // Разрешаем передачу
    UCSR0B = (1 << TXEN0);
    // Устанавливаем формат кадра: 8 бит данных, 1 стоп-бит
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Функция для передачи одного байта данных
void UART_transmit(unsigned char data) {
    // Ожидаем, пока освободится буфер передачи
    while (!(UCSR0A & (1 << UDRE0)));
    // Помещаем данные в буфер
    UDR0 = data;
}
// Функция для передачи строки
void UART_putstring(const char *s) {
    while (*s) {
        UART_transmit(*s++);
    }
}
// Millis function
unsigned long millis() {
    unsigned long ms;
    // Disable interrupts temporarily to prevent variable corruption
    cli();
    ms = milliseconds;
    sei();
    return ms;
}
// Initialize rotary encoder
void rotary_encoder_init() {
    // Set encoder pins as inputs with pull-ups
    DDRB &= ~(1 << ENCODER_PIN1); // Set PB1 as input
    DDRB &= ~(1 << ENCODER_PIN2); // Set PB2 as input
    PORTB |= (1 << ENCODER_PIN1); // Enable pull-up on PB1
    PORTB |= (1 << ENCODER_PIN2); // Enable pull-up on PB2
    // Initialize state
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    oldState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    // Initialize position
    position = 0;
    positionExt = 0;
    positionExtPrev = 0;
    positionExtTime = 0;
    positionExtTimePrev = 0;
}
// Read the encoder and update position
void rotary_encoder_tick() {
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    int8_t thisState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    if (oldState != thisState) {
        position += KNOBDIR[thisState | (oldState << 2)];
        oldState = thisState;
        if (thisState == LATCH0 || thisState == LATCH3) {
            positionExt = position >> 2;
            positionExtTimePrev = positionExtTime;
            positionExtTime = millis();
        }
    }
}
// Get the current position
long rotary_encoder_get_position() {
    return positionExt;
}
// Get time between rotations
unsigned long rotary_encoder_get_millis_between_rotations() {
    return (positionExtTime - positionExtTimePrev);
}
// Get RPM
unsigned long rotary_encoder_get_rpm() {
    unsigned long timeBetweenLastPositions = positionExtTime - positionExtTimePrev;
    unsigned long timeToLastPosition = millis() - positionExtTime;
    unsigned long t = (timeBetweenLastPositions > timeToLastPosition) ? timeBetweenLastPositions : timeToLastPosition;
    return 60000UL / (t * 20);
}
void setup(){
    UART_init(MYUBRR); // Инициализируем UART
    // Initialize encoder and timer
    rotary_encoder_init();
    // Set up Timer0 for millis()
    TCCR0A = (1 << WGM01); // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler 64
    OCR0A = 249; // Set compare value for 1ms interrupt at 16MHz
    TIMSK0 = (1 << OCIE0A); // Enable Timer0 compare interrupt
    sei(); // Enable global interrupts
}
void loop(){
    long pos = 0;
    long newPos;
    char buffer[16]; // Буфер для хранения строки
    while (1) {
        rotary_encoder_tick();
        newPos = rotary_encoder_get_position();
        if (pos != newPos) {
            // Преобразование newPos в строку и вывод через UART
            itoa(newPos, buffer, 10);
            UART_putstring(buffer);
            UART_putstring("\n");
            pos = newPos;
        }
    }
}
// Main function
int main(void) {
    setup();
    loop();
}
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds++;
}
 
 
По-моему, здесь все прекрасно. Тут даже есть свой собственный millis() с блэкджеком и шлюхами! Красота! )
 
А вот еще версия на внешних прерываниях. Кстати, я так и не смог добиться от известного пиздабол-чата, чтобы он мне сделал что-то подобное иcходя из примера выше. Пришлось переписывать вручную:
 
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// Настройки UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// Define encoder pins
#define ENCODER_PIN1 PB1
#define ENCODER_PIN2 PB2
#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3
long pos = 0;
long newPos;
char buffer[16]; // Буфер для хранения строки
// Rotary encoder variables
volatile int8_t oldState;
volatile long position;
volatile long positionExt;
volatile long positionExtPrev;
unsigned long positionExtTime;
unsigned long positionExtTimePrev;
// Global millisecond counter
volatile unsigned long milliseconds = 0;
const int8_t KNOBDIR[] = {
    0, -1, 1, 0,
    1, 0, 0, -1,
    -1, 0, 0, 1,
    0, 1, -1, 0
};
// Преобразование числа в строку
char* itoa(int value, char* buffer, int base) {
    if (base < 2 || base > 36) {
        *buffer = '\0';
        return buffer;
    }
    char* ptr = buffer, *ptr1 = buffer, tmp_char;
    int tmp_value;
    if (value < 0 && base == 10) {
        value = -value;
        *ptr++ = '-';
    }
    tmp_value = value;
    do {
        int remainder = tmp_value % base;
        *ptr++ = (remainder < 10) ? (remainder + '0') : (remainder - 10 + 'a');
    } while (tmp_value /= base);
    if (*buffer == '-') {
        ptr1++;
    }
    *ptr-- = '\0';
    while (ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr-- = *ptr1;
        *ptr1++ = tmp_char;
    }
    return buffer;
}
// Функция для инициализации UART
void UART_init(unsigned int ubrr) {
    // Устанавливаем скорость передачи
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;
    // Разрешаем передачу
    UCSR0B = (1 << TXEN0);
    // Устанавливаем формат кадра: 8 бит данных, 1 стоп-бит
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Функция для передачи одного байта данных
void UART_transmit(unsigned char data) {
    // Ожидаем, пока освободится буфер передачи
    while (!(UCSR0A & (1 << UDRE0)));
    // Помещаем данные в буфер
    UDR0 = data;
}
// Функция для передачи строки
void UART_putstring(const char *s) {
    while (*s) {
        UART_transmit(*s++);
    }
}
// Millis function
unsigned long millis() {
    unsigned long ms;
    // Disable interrupts temporarily to prevent variable corruption
    cli();
    ms = milliseconds;
    sei();
    return ms;
}
// Initialize rotary encoder
void rotary_encoder_init() {
    // Set encoder pins as inputs with pull-ups
    DDRB &= ~(1 << ENCODER_PIN1); // Set PB1 as input
    DDRB &= ~(1 << ENCODER_PIN2); // Set PB2 as input
    PORTB |= (1 << ENCODER_PIN1); // Enable pull-up on PB1
    PORTB |= (1 << ENCODER_PIN2); // Enable pull-up on PB2
    //Настройка прерываний
    // Включаем прерывания на порту B
    PCICR |= (1 << PCIE0); // Enable PCINT0 (Port B)
    PCMSK0 |= (1 << PCINT1) | (1 << PCINT2);   // Включаем прерывания для конкретных пинов PB1 и PB2    
    // Initialize state
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    oldState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    // Initialize position
    position = 0;
    positionExt = 0;
    positionExtPrev = 0;
    positionExtTime = 0;
    positionExtTimePrev = 0;
}
// Read the encoder and update position
void rotary_encoder_tick() {
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    int8_t thisState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    if (oldState != thisState) {
        position += KNOBDIR[thisState | (oldState << 2)];
        oldState = thisState;
        if (thisState == LATCH0 || thisState == LATCH3) {
            positionExt = position >> 2;
            positionExtTimePrev = positionExtTime;
            positionExtTime = millis();
        }
    }
}
// Get the current position
long rotary_encoder_get_position() {
    return positionExt;
}
// Get time between rotations
unsigned long rotary_encoder_get_millis_between_rotations() {
    return (positionExtTime - positionExtTimePrev);
}
// Get RPM
unsigned long rotary_encoder_get_rpm() {
    unsigned long timeBetweenLastPositions = positionExtTime - positionExtTimePrev;
    unsigned long timeToLastPosition = millis() - positionExtTime;
    unsigned long t = (timeBetweenLastPositions > timeToLastPosition) ? timeBetweenLastPositions : timeToLastPosition;
    return 60000UL / (t * 20);
}
void setup(){
    UART_init(MYUBRR); // Инициализируем UART
    // Initialize encoder and timer
    rotary_encoder_init();
    // Set up Timer0 for millis()
    TCCR0A = (1 << WGM01); // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler 64
    OCR0A = 249; // Set compare value for 1ms interrupt at 16MHz
    TIMSK0 = (1 << OCIE0A); // Enable Timer0 compare interrupt
    sei(); // Enable global interrupts
}
void EN(){
    rotary_encoder_tick();
    newPos = rotary_encoder_get_position();
    if (pos != newPos) {
        // Преобразование newPos в строку и вывод через UART
        itoa(newPos, buffer, 10);
        UART_putstring(buffer);
        UART_putstring("\n");
        pos = newPos;
    }
}
void loop(){
 
    while (1) {
 
    }
}
// Main function
int main(void) {
    setup();
    loop();
}
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds++;
}
// Обработчик прерывания PCINT
ISR(PCINT0_vect) {
    EN();
}

А вот и ускоряемая крутилка с ограничением отчета от 0 до 9999:

/*
 * Энкодер с ускоряемой крутилкой и ограничителем счетчика от 0 до 9999
 * С version
 * https://madmentat.ru
 * 2024
 */
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdbool.h>
// Настройки UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// Define encoder pins
#define ENCODER_PIN1 PB1
#define ENCODER_PIN2 PB2
#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3
long pos = 0;
long newPos;
char buffer[16]; // Буфер для хранения строки
// Rotary encoder variables
volatile int8_t oldState;
volatile long position;
volatile long positionExt;
volatile long positionExtPrev;
unsigned long positionExtTime;
unsigned long positionExtTimePrev;
// Global millisecond counter
volatile unsigned long milliseconds1 = 0;
volatile unsigned long milliseconds2 = 0;
 
volatile int Mu = 1; // Глобальная переменная для множителя
int8_t KNOBDIR[16]; // Динамический массив для значений
void updateKnobDir() {
    KNOBDIR[0] = 0;
    KNOBDIR[1] = -Mu;
    KNOBDIR[2] = Mu;
    KNOBDIR[3] = 0;
    KNOBDIR[4] = Mu;
    KNOBDIR[5] = 0;
    KNOBDIR[6] = 0;
    KNOBDIR[7] = -Mu;
    KNOBDIR[8] = -Mu;
    KNOBDIR[9] = 0;
    KNOBDIR[10] = 0;
    KNOBDIR[11] = Mu;
    KNOBDIR[12] = 0;
    KNOBDIR[13] = Mu;
    KNOBDIR[14] = -Mu;
    KNOBDIR[15] = 0;
}
// Преобразование числа в строку
char* itoa(int value, char* buffer, int base) {
    if (base < 2 || base > 36) {
        *buffer = '\0';
        return buffer;
    }
    char* ptr = buffer, *ptr1 = buffer, tmp_char;
    int tmp_value;
    if (value < 0 && base == 10) {
        value = -value;
        *ptr++ = '-';
    }
    tmp_value = value;
    do {
        int remainder = tmp_value % base;
        *ptr++ = (remainder < 10) ? (remainder + '0') : (remainder - 10 + 'a');
    } while (tmp_value /= base);
    if (*buffer == '-') {
        ptr1++;
    }
    *ptr-- = '\0';
    while (ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr-- = *ptr1;
        *ptr1++ = tmp_char;
    }
    return buffer;
}
// Функция для инициализации UART
void UART_init(unsigned int ubrr) {
    // Устанавливаем скорость передачи
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;
    // Разрешаем передачу
    UCSR0B = (1 << TXEN0);
    // Устанавливаем формат кадра: 8 бит данных, 1 стоп-бит
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Функция для передачи одного байта данных
void UART_transmit(unsigned char data) {
    // Ожидаем, пока освободится буфер передачи
    while (!(UCSR0A & (1 << UDRE0)));
    // Помещаем данные в буфер
    UDR0 = data;
}
// Функция для передачи строки
void UART_putstring(const char *s) {
    while (*s) {
        UART_transmit(*s++);
    }
}
// Millis function
unsigned long millis() {
    unsigned long ms;
    // Disable interrupts temporarily to prevent variable corruption
    cli();
    ms = milliseconds1;
    sei();
    return ms;
}
// Initialize rotary encoder
void rotary_encoder_init() {
    // Set encoder pins as inputs with pull-ups
    DDRB &= ~(1 << ENCODER_PIN1); // Set PB1 as input
    DDRB &= ~(1 << ENCODER_PIN2); // Set PB2 as input
    PORTB |= (1 << ENCODER_PIN1); // Enable pull-up on PB1
    PORTB |= (1 << ENCODER_PIN2); // Enable pull-up on PB2
    //Настройка прерываний
    // Включаем прерывания на порту B
    PCICR |= (1 << PCIE0); // Enable PCINT0 (Port B)
    PCMSK0 |= (1 << PCINT1) | (1 << PCINT2);   // Включаем прерывания для конкретных пинов PB1 и PB2
    // Initialize state
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    oldState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    // Initialize position
    position = 0;
    positionExt = 0;
    positionExtPrev = 0;
    positionExtTime = 0;
    positionExtTimePrev = 0;
}
// Read the encoder and update position
void rotary_encoder_tick() {
    uint8_t sig1 = PINB & (1 << ENCODER_PIN1);
    uint8_t sig2 = PINB & (1 << ENCODER_PIN2);
    int8_t thisState = (sig1 >> ENCODER_PIN1) | ((sig2 >> ENCODER_PIN2) << 1);
    if (oldState != thisState) {
        position += KNOBDIR[thisState | (oldState << 2)];
        oldState = thisState;
        if (thisState == LATCH0 || thisState == LATCH3) {
            positionExt = position >> 2;
            positionExtTimePrev = positionExtTime;
            positionExtTime = millis();
        }
    }
}
// Get the current position
long rotary_encoder_get_position() {
    return positionExt;
}
// Get time between rotations
unsigned long rotary_encoder_get_millis_between_rotations() {
    return (positionExtTime - positionExtTimePrev);
}
// Get RPM
unsigned long rotary_encoder_get_rpm() {
    unsigned long timeBetweenLastPositions = positionExtTime - positionExtTimePrev;
    unsigned long timeToLastPosition = millis() - positionExtTime;
    unsigned long t = (timeBetweenLastPositions > timeToLastPosition) ? timeBetweenLastPositions : timeToLastPosition;
    return 60000UL / (t * 20);
}
void setup(){
    UART_init(MYUBRR); // Инициализируем UART
    // Initialize encoder and timer
    rotary_encoder_init();
    // Set up Timer0 for millis()
    TCCR0A = (1 << WGM01); // CTC mode
    TCCR0B = (1 << CS01) | (1 << CS00);  // Prescaler 64
    OCR0A = 249; // Set compare value for 1ms interrupt at 16MHz
    TIMSK0 = (1 << OCIE0A); // Enable Timer0 compare interrupt
    sei(); // Enable global interrupts
}
long tickCount =     0;
bool tickFlag  = false;
volatile uint8_t lastTickCount = 0; // Последнее сохраненное значение tickCount
void EN(){
    tickFlag     = true;
    tickCount++;
    updateKnobDir();
    // Обновление позиции энкодера
    rotary_encoder_tick();
    newPos = rotary_encoder_get_position(); // Получение текущей позиции
    // Ограничение счетчика от 0 до 9999
    if (pos != newPos) {
        if(position <= 0){
        position = 0;
        tickFlag = false;
        Mu = 1;
    }
    if(position  >= 39996) {
         position = 39996;
         tickFlag = false;
         Mu = 1;
    }
        // Преобразование newPos в строку и вывод через UART
        itoa(newPos, buffer, 10);
        UART_putstring(buffer);
        UART_putstring("\n");
        pos = newPos;
    }
}
void loop(){
    while (1) {
        if (tickFlag == true) {
            tickFlag = false;
            milliseconds2 = 0;
        }
        if (tickCount > 80) {
            if (milliseconds2 < 200) {
                Mu = Mu * 10;
                // Ограничение множителя
                if (Mu > 100) {
                    Mu = 100;
                }
                updateKnobDir();
                tickCount = 0;
                milliseconds2 = 0; // Сброс таймера
            }
        }
        // Проверка, прошло ли 1000 миллисекунд (1 секунда)
        if (milliseconds2 > 150) {
            milliseconds2 = 0; // Сброс таймера
            // Проверка изменения tickCount
            if (tickCount == lastTickCount) {
                // tickCount не изменилась за 1 секунду
                tickFlag = false;
                tickCount = 0;
                Mu = 1;
                updateKnobDir();
            }
            // Сохранение текущего значения tickCount для следующего цикла
            lastTickCount = tickCount;
        }
    }
}
// Main function
int main(void) {
    setup();
    loop();
}
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds1++;
    milliseconds2++;
}
// Обработчик прерывания PCINT
ISR(PCINT0_vect) {
    EN();
}