ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Понедельник
21 апреля
1503980 Топик полностью
Nikolay_Po (06.03.2025 18:00 - 18:11, просмотров: 148) ответил Andreas на Ужасы нашего городка. После года работы иногда стали энкодеры(те, что руками крутишь) глючить. Решил посмотреть скопом и логанализатором и удивился, что вообще работает. Подтяжка 5к к питанию без конденсатора. Добавление конденсатора полностью решает проблему, но надо как-то доработать старое программно.
У меня не вызывает сомнения, что проблему, пока контакты срабатывают хоть как-то, можно решить в два этапа: 1) Фильтруем импульсы; 2) Декодируем позицию конечным автоматом по таблице. 

Вот образец рабочего кода из проекта с мотором и энкодером на редукторе, рассчитанным на работу в условиях высокого уровня ЭМП.


#define EncAbit PIND6 //Rotary encoder A input is D6 (PD6)
#define EncBbit PIND7 //Rotary encoder B input is D7 (PD7)

volatile int16_t PositionActual = 0; //Actual position in steps if !PositionUnknown, else not valid

volatile uint8_t DebounceCNT0 = 0; //Debounce "vertical" counters
volatile uint8_t DebounceCNT1 = 0; //
volatile uint8_t DebounceState = 0; //Debounce status (filtered inputs)
volatile uint8_t DebounceToggle = 0; //Toggle flags. Set if the state changed and stay steady for 4 accuisition periods

//Rotary encoder signal decoder, the table and algorithm (c) Blade (Klaus Varis?).
//From here: https://www.avrfreaks.net/sites/default/files/project_files/Rotary%20decoder.pdf
//https://community.atmel.com/projects/quadrature-rotary-decoder
const uint8_t LookupTable[256] = { //__attribute__((progmem))
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00,
  0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00,
  0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void EncoderSetZero(void) { //Reset position for current encoder state
  uint8_t DecoderInitialState;
  switch (DebounceState >> 6) { //Get two bits of current filtered encoder levels
    case 0b00:
      DecoderInitialState = 0x78;
      break;
    case 0b01:
      DecoderInitialState = 0xE1;
      break;
    case 0b10:
      DecoderInitialState = 0x1E;
      break;
    case 0b11:
      DecoderInitialState = 0x87;
      break;
  }
  cli();
  DecorderState = DecoderInitialState; //Initialize decoder pre-history
  PositionActual = 0; //Reset the position
  EncoderErrors = 0; //Clear error counter
  AlarmsActive.AsBit.PositionUnknown = 0; //Clear active alarm
  Status.AsBit.HomingReq = 0; //Mark homing complete
  sei();
}

ISR(TIMER1_OVF_vect) {
  //Debounce inputs
  uint8_t Delta = (PIND & 0b11111100) ^ DebounceState; //Choose PIND7:PIND2 bits for debounce and compare with previous steady state, see pin designation in header file
  DebounceCNT1 = (DebounceCNT1 ^ DebounceCNT0) & Delta; //https://www.compuphase.com/electronics/debouncing.htm Copyright 2019, Thiadmer Riemersma
  DebounceCNT0 = ~DebounceCNT0 & Delta;
  DebounceToggle = Delta & ~(DebounceCNT0 | DebounceCNT1); //State change flags. "1" means a change just occured.
  DebounceState ^= DebounceToggle; //Debounced discrete signals
  //Read and decode rotary encoder pulses
  if (DebounceToggle & ((1 << EncAbit) | (1 << EncBbit))) { //Check was there an encoder bit toggle or not?
    //Encoder change detected. Validate the move
    DecorderState = (DecorderState << 2) | (DebounceState >> 6); //Throw two old then add two fresh bits of encoder output (AB)
    int8_t DecodeResult = (int8_t)LookupTable[DecorderState]; //Determine encoder state and position change.
    PositionActual += (int16_t)DecodeResult; //Account the position
    if (DecodeResult == 0) { //Check for possible errors
      EncoderErrors++;
      if (EncoderErrors >= EncoderErrThreshold) {
        AlarmsActive.AsBit.PositionUnknown = 1; //Mark the position as not valid
        Status.AsBit.HomingReq = 1; //Request new homing to clear the errors
        Status.AsBit.Motion = 0; //Stop motion until a command
        if (EncoderErrors >= 255) {
          EncoderErrors = 255;
        }
      }
    }
  }
  //Discrete signals sampled, filtered and corresponding flags are updated.
}


Код выдран из контекста, поэтому в лоб не скомпилируется. Но идея, надеюсь, ясна. К сожалению, ссылки на оригинал фильтра энкодера не действительны.

По примеру. В примере удаляется дребезг сразу шести контактов - двух контактов управления туда-сюда, двух концевиков и двух сигналов энкодера. Фильтрация работает на базе "вертикальных счётчиков". Для большей степени фильтрации, нужно добавить ещё уровней DebounceCNT (на счёт что с чем сравнивать для принятия решения о переключении сигнала - не уточню, нужно вникать).

После фильтрации, анализируем состояние бит энкодера. Таблица на 256 предварительных состояний (это получается ретроспектива на 8/2=4 состояния назад), с текущим получается пятое, можно сделать мажорирование попытку определения направления и факта поворота.