КРУТИЛКА ЭНКОДЕРА И СЕМИСЕГМЕНТНЫЙ ИНДИКАТОР НА ОСНОВЕ МИКРОСХЕМЫ TM1637

 

/*
 * TM1637 Control by encoder program 
 * С version
 * https://madmentat.ru
 * 2024
 */
#define F_CPU 16000000UL  // Определяем частоту процессора как 16 МГц
#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <avr/interrupt.h>
#define CLK_PIN PC0
#define DIO_PIN PC1
#define BUTTON_PIN PD2
#define SEG_A   0b00000001
#define SEG_B   0b00000010
#define SEG_C   0b00000100
#define SEG_D   0b00001000
#define SEG_E   0b00010000
#define SEG_F   0b00100000
#define SEG_G   0b01000000
#define DEFAULT_BIT_DELAY  100
#define TM1637_I2C_COMM1    0x40
#define TM1637_I2C_COMM2    0xC0
#define TM1637_I2C_COMM3    0x80
// Настройки 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
};
int number = 1982; // Начальное число
// Преобразование числа в строку
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);
}
const uint8_t digitToSegment[] = {
    0b00111111,    // 0
    0b00000110,    // 1
    0b01011011,    // 2
    0b01001111,    // 3
    0b01100110,    // 4
    0b01101101,    // 5
    0b01111101,    // 6
    0b00000111,    // 7
    0b01111111,    // 8
    0b01101111,    // 9
    0b01110111,    // A
    0b01111100,    // b
    0b00111001,    // C
    0b01011110,    // d
    0b01111001,    // E
    0b01110001     // F
};
static const uint8_t minusSegments = 0b01000000;
void bitDelay(void);
void start(void);
void stop(void);
bool writeByte(uint8_t b);
void setBrightness(uint8_t brightness, bool on);
void setSegments(const uint8_t segments[], uint8_t length, uint8_t pos);
void clear(void);
void showNumberDec(int num, bool leading_zero, uint8_t length, uint8_t pos);
void showNumberDecEx(int num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos);
void showNumberHexEx(uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos);
void showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos);
void showDots(uint8_t dots, uint8_t* digits);
void bitDelay(void) {
    _delay_us(DEFAULT_BIT_DELAY);
}
void start(void) {
    DDRC |= (1 << DIO_PIN);  // Установить DIO_PIN как выход
    bitDelay();
}
void stop(void) {
    DDRC |= (1 << DIO_PIN);  // Установить DIO_PIN как выход
    bitDelay();
    DDRC &= ~(1 << CLK_PIN); // Установить CLK_PIN как вход
    bitDelay();
    DDRC &= ~(1 << DIO_PIN); // Установить DIO_PIN как вход
    bitDelay();
}
bool writeByte(uint8_t b) {
    uint8_t data = b;
    for (uint8_t i = 0; i < 8; i++) {
        DDRC |= (1 << CLK_PIN);  // Установить CLK_PIN как выход
        bitDelay();
        if (data & 0x01)
            DDRC &= ~(1 << DIO_PIN);  // Установить DIO_PIN как вход
        else
            DDRC |= (1 << DIO_PIN);   // Установить DIO_PIN как выход
        bitDelay();
        DDRC &= ~(1 << CLK_PIN);  // Установить CLK_PIN как вход
        bitDelay();
        data >>= 1;
    }
    DDRC |= (1 << CLK_PIN);  // Установить CLK_PIN как выход
    DDRC &= ~(1 << DIO_PIN); // Установить DIO_PIN как вход
    bitDelay();
    DDRC &= ~(1 << CLK_PIN);  // Установить CLK_PIN как вход
    bitDelay();
    uint8_t ack = PINC & (1 << DIO_PIN);
    if (ack == 0)
        DDRC |= (1 << DIO_PIN); // Установить DIO_PIN как выход
    bitDelay();
    DDRC |= (1 << CLK_PIN);  // Установить CLK_PIN как выход
    bitDelay();
    return ack;
}
void setBrightness(uint8_t brightness, bool on) {
    //uint8_t m_brightness = (brightness & 0x7) | (on ? 0x08 : 0x00);
    // Здесь можно добавить код для применения яркости
}
void setSegments(const uint8_t segments[], uint8_t length, uint8_t pos) {
    start();
    writeByte(TM1637_I2C_COMM1);
    stop();
    start();
    writeByte(TM1637_I2C_COMM2 + (pos & 0x03));
    for (uint8_t k = 0; k < length; k++)
        writeByte(segments[k]);
    stop();
    start();
    writeByte(TM1637_I2C_COMM3 + (0x0f & 0x0f)); // Используем максимальную яркость
    stop();
}
void clear(void) {
    uint8_t data[] = { 0, 0, 0, 0 };
    setSegments(data, 4, 0);
}
void showNumberDec(int num, bool leading_zero, uint8_t length, uint8_t pos) {
    showNumberDecEx(num, 0, leading_zero, length, pos);
}
void showNumberDecEx(int num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) {
    showNumberBaseEx(num < 0 ? -10 : 10, num < 0 ? -num : num, dots, leading_zero, length, pos);
}
void showNumberHexEx(uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) {
    showNumberBaseEx(16, num, dots, leading_zero, length, pos);
}
void showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) {
    bool negative = false;
    if (base < 0) {
        base = -base;
        negative = true;
    }
    uint8_t digits[4];
    if (num == 0 && !leading_zero) {
        for (uint8_t i = 0; i < (length - 1); i++)
            digits[i] = 0;
        digits[length - 1] = digitToSegment[0];
    } else {
        for (int i = length - 1; i >= 0; --i) {
            uint8_t digit = num % base;
            if (digit == 0 && num == 0 && !leading_zero)
                digits[i] = 0;
            else
                digits[i] = digitToSegment[digit & 0x0f];
            if (digit == 0 && num == 0 && negative) {
                digits[i] = minusSegments;
                negative = false;
            }
            num /= base;
        }
        if (dots != 0)
            showDots(dots, digits);
    }
 
    setSegments(digits, length, pos);
}
void showDots(uint8_t dots, uint8_t* digits) {
    for (int i = 0; i < 4; ++i) {
        digits[i] |= (dots & 0x80);
        dots <<= 1;
    }
}
void setup() {
    UART_init(MYUBRR); // Инициализация UART
    rotary_encoder_init(); // Инициализация энкодера
 
    // Установить начальное значение position для начала с 1982
    pos = 0;
    newPos = 1982;
    position = 1982 * 4; // Если каждый шаг энкодера соответствует одному числу
 
    // Настройка таймера для millis()
    TCCR0A = (1 << WGM01); // CTC режим
    TCCR0B = (1 << CS01) | (1 << CS00); // Предделитель 64
    OCR0A = 249; // Установка значения для сравнения, чтобы прерывание срабатывало каждую 1 мс на 16 МГц
    TIMSK0 = (1 << OCIE0A); // Включение прерывания таймера
    sei(); // Разрешение глобальных прерываний
 
    // Установка пинов для CLK и DIO как выходы
    DDRC |= (1 << CLK_PIN) | (1 << DIO_PIN);
 
    // Установка пина кнопки как вход с подтяжкой
    DDRD &= ~(1 << BUTTON_PIN);  // Установить PD2 как вход
    PORTD |= (1 << BUTTON_PIN);  // Включить внутренний подтягивающий резистор на PD2
 
    setBrightness(0x0f, true); // Установка максимальной яркости
 
    // Обновление дисплея начальным значением
    showNumberDec(newPos, true, 4, 0); // Отображение начального числа с ведущими нулями
 
    // Вывод начального значения на UART
    itoa(newPos, buffer, 10);
    UART_putstring(buffer);
    UART_putstring("\n");
}
 
void loop() {
    //static bool buttonPressed = false; // Для отслеживания состояния кнопки
 
    while (1) {
        // Обновление позиции энкодера
        rotary_encoder_tick();
        newPos = rotary_encoder_get_position(); // Получение текущей позиции
        // Ограничение значения
        if (newPos < 0) newPos = 0;
        if (newPos > 9999) newPos = 9999;
        // Проверка на изменение позиции
        if (pos != newPos) {
            pos = newPos;
            number = newPos; // Теперь `number` и `pos` синхронизированы
            showNumberDec(number, true, 4, 0); // Обновление дисплея
            itoa(number, buffer, 10); // Вывод значения в UART
            UART_putstring(buffer);
            UART_putstring("\n");
        }
    }
}
 
 
 
int main(void) {  
    setup();
    loop();
    return 0;
}
 
// Timer0 Compare Match A ISR
ISR(TIMER0_COMPA_vect) {
    // Increment the millisecond counter
    milliseconds++;
}