Энкодер на Attiny85

Тема в разделе "Глядите, что я сделал", создана пользователем ostrov, 14 ноя 2016.

  1. ostrov

    ostrov Гуру

    Задался целью вынести энкодер на отдельное устройство, которое не грузило бы основной МК, а передавало бы на него готовое положение (в последнее время вообще придерживаюсь такой тактики - разделять задачи по разным устройствам, уже приличная библиотека наработалась). Причем, сделать это в минимал-стайл, не то чтобы я пожалел МК покруче, но что то не дало мне выделить под такую плевую задачу даже Мегу8, но говоря уже о ПроМини. Да и размер иногда имеет значение. Может кому понадобится, ниже программа со словарным описанием подключения, можно перемотать сразу туда. )

    На первый взгляд задача действительно казалась плевой: стандраное решение - повесить на прерывание по таймеру опрос одной ножки и при изменении ее состояния опрашивать вторую. При изменении счетчика передавать данные наружу но не чаще чем пять раз в секунду. Проблема появилась в выводе результата. Аппаратного UART на Attiny85 нет. Софтварный библиотечный не лезет, да и не работает. Пришлось имитировать его вручную варварским но эффективным способом при помощи millis() и delay(). Но тут обозначились грабли в виде конфиликта таймеров. Пришлось переносить энкодерное прерывание на довольно своеобразный второй таймер. В итоге появился гибрид, который работает вполне устойчиво. Размер 790 байт, так что влез бы и в Attiny13, наверное.

    В данном примере количество шагов ограничено двухзначным числом в плюс и в минус, изменить под себя можно запросто хоть увеличением диапазона, хоть отсеканием, хоть зацикливанием. Повысить скорость обработки, точность и стабильность, при необходимости, путем добавления внешнего кварца на 16 МГц (у меня сделано на внутреннем генераторе 8 МГц). Прибавить обработчик нажатия кнопки тоже не проблема, можно выводить отдельной командой.



    Код (C++):
    #include <util/delay.h> // для работы с микросекундами и экономии места
    #define timPrint 200    // минимальная пауза для передачи
    #define TX_PIN 4        // ножка для передачи
    // энкодер подключается к пинам 0 и 1 притянутым к +5В через резисторы 10КОм
    // настроено под внутренний резонатор 8 МГц, фьюзы: 0xE2, 0xDF, CKDIV8 - off

    int Step = 0;           // счетчик
    int oldStep = 0;
    byte p[3];              // буффер
    unsigned long timerShow = 0;

    void setup() {
      DDRB = 0b00010000;    // TX на выход, остальные на вход
      TCCR1 = 0b00000111;   // режим CTC, делитель 64
      OCR1A = 0x2B;         // 350 мкс
      TIMSK |= 0b01000000;  // запуск таймера по совпадению 1А не меняя остальных настроек
    }

    void loop() {
      if (timerShow < millis()) {
        if (Step != oldStep) {
          int St = Step;
          if (Step < 0) {
            UART_byte('-');
            St = abs(Step);
          }
          byte s1 = St / 10 + 0x30;
          byte s2 = St % 10 + 0x30;
          UART_byte(0x00);
          UART_byte(s1);
          UART_byte(s2);
          UART_byte('\n');
          UART_byte(0x00);
        }
        oldStep = Step;
        timerShow = millis() + timPrint;
      }
    }


    ISR(TIMER1_COMPA_vect)  //обработчик прерывания таймера 1 по совпадению А
    {
      p[0] = p[1];
      p[1] = p[2];
      p[2] = PINB & 0x01;
      if (p[0] && p[1] && !p[2]) {
        (PINB & 0x02) ? Step++ : Step--;
      }
      TCNT1 = 0;
    }

    void UART_byte(byte a) {        //отправка байта
      UART_bit(0x00);//start bit
      for (int i = 0; i < 8; i++) {
        if ((a >> i) & 0x01) {
          UART_bit(0x01); //data bit
        } else {
          UART_bit(0x00); //data bit
        }
      }
      UART_bit(0x01);     //stop bit
    }

    void UART_bit(byte b) {         //отправка бита
      if (b == 0x00) {
        PORTB &= ~(1 << TX_PIN);
      }
      _delay_ms(0.104);
      PORTB |= (1 << TX_PIN);
    }
     
    Последнее редактирование: 14 ноя 2016
    Alex19, Tomasina и ИгорьК нравится это.
  2. Tomasina

    Tomasina Сушитель лампочек Модератор