ЭНКОДЕР В MICROCHIP STUDIO
Вот с этой программой я здорово наебался. Пробовал разные варианты, в том числе всякие алгоритмы на основе Гайверовских, и чат ГПТ никак не мог помочь. Все его поделки допускали прокруты, "холостой ход", то есть, при повороте крутилки происходит характерный щелчок - в 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(); }