Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Обработка дребезга энкодера
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
GoodNews
Если энкодер вращать влево - результат 100% стабильный и правильный. Если же вращать вправо, при этом делать это достаточно быстро, то иногда проскакивает результат, будто-бы я вращал его влево. Вроде бы сделать проверку 2 из 3 (т.е. если два события были одинаковыми, а третье нет, то брать только одинаковое событие), а всё-равно появляется "левое" значение. Как быть?
encoder.c
CODE

#include <avr/io.h>
#include <avr/interrupt.h>
#include "encoder.h"

//Состояния энкодера, которые возвращает подпрограмма
#define ENCODER_LEFT_TURN -1
#define ENCODER_RIGHT_TURN 1

//Определяем состояния энкодера
#define STATE_LEFT 0x08 //Поворот налево
#define STATE_CENTER 0x00 //Состояние покоя
#define STATE_RIGHT 0x10 //Поворот направо

enum {
ENCODER_WAIT,
ENCODER_DEBOUNCE,
ENCODER_WAIT_TURN
};

uint8_t ENCODER_TASK_STATE = ENCODER_WAIT; //Статус энкодера для обработки дребезга
uint8_t ENCODER_TURNED = 0; //По-умолчанию энкодер не менял состояния

//Сюда помещаются данные энкодера для обработки дребезга
static uint8_t ENCODER_PREV_STATE;
static uint8_t ENCODER_MIDDLE_STATE;
static uint8_t ENCODER_LAST_STATE;

volatile uint8_t ENCODER_DEBOUNCE_COUNTER = 0;
volatile int8_t ENCODER_STATE; //Сюда получаем состояние энкодера после обработки в прерывании
volatile uint8_t isEncoderTurned = 0; //Если энкодер изменил состояние, флаг будет равен 1

//Определяем буфер для хранения состояний
static int8_t ENCODER_BUFFER = 0;

void ENCODER_TASK(void)
{
switch (ENCODER_TASK_STATE)
{
case ENCODER_WAIT: //Ждём изменения состояния энкодера
if (READ_ENCODER_STATE() == STATE_CENTER)
{
ENCODER_TURNED = 0;
break;
}
ENCODER_PREV_STATE = READ_ENCODER_STATE();
ENCODER_DEBOUNCE_COUNTER = 0;
ENCODER_TASK_STATE = ENCODER_DEBOUNCE;
break;
case ENCODER_DEBOUNCE: //Убираем дребезг
ENCODER_MIDDLE_STATE = READ_ENCODER_STATE();
if (ENCODER_MIDDLE_STATE != ENCODER_PREV_STATE)
{
ENCODER_DEBOUNCE_COUNTER = 0;
ENCODER_TURNED = 0;
ENCODER_TASK_STATE = ENCODER_WAIT;
break;
}
ENCODER_LAST_STATE = READ_ENCODER_STATE();
if (ENCODER_LAST_STATE != ENCODER_MIDDLE_STATE)
{
ENCODER_DEBOUNCE_COUNTER = 0;
ENCODER_TURNED = 0;
ENCODER_TASK_STATE = ENCODER_WAIT;
break;
}
if (ENCODER_DEBOUNCE_COUNTER > 20)
{
ENCODER_BUFFER = ENCODER_STATE;
isEncoderTurned = 1;
ENCODER_TASK_STATE = ENCODER_WAIT_TURN;
}
break;
case ENCODER_WAIT_TURN:
if (READ_ENCODER_STATE() != STATE_CENTER)
{
ENCODER_DEBOUNCE_COUNTER = 0;
break;
}
if (ENCODER_LAST_STATE == ENCODER_MIDDLE_STATE)
if (ENCODER_LAST_STATE != ENCODER_BUFFER)
ENCODER_BUFFER = ENCODER_LAST_STATE;
if (ENCODER_MIDDLE_STATE == ENCODER_PREV_STATE)
if (ENCODER_MIDDLE_STATE != ENCODER_BUFFER)
ENCODER_BUFFER = ENCODER_MIDDLE_STATE;
if (ENCODER_PREV_STATE == ENCODER_LAST_STATE)
if (ENCODER_PREV_STATE != ENCODER_BUFFER)
ENCODER_BUFFER = ENCODER_PREV_STATE;
if (ENCODER_DEBOUNCE_COUNTER > 80)
{
ENCODER_TASK_STATE = ENCODER_WAIT;
ENCODER_TURNED = 0;
}
}
}
int8_t encoder_state(void)
{
int8_t returnState = ENCODER_NONE;
if (isEncoderTurned)
{
switch (ENCODER_BUFFER)
{
case STATE_CENTER:
returnState = ENCODER_NONE;
break;
case STATE_LEFT:
returnState = ENCODER_LEFT_TURN;
break;
case STATE_RIGHT:
returnState = ENCODER_RIGHT_TURN;
break;
default:
returnState = ENCODER_NONE;
}
isEncoderTurned = 0;
}
return returnState;
}

void encoder_init(void)
{
//Нам необходимо модифицировать только эти биты порта
ENCODER_REG &= ~((ENCODER_LEFT)|(ENCODER_RIGHT));
//Порт энкодера - входы с подтяжкой (т.к. соответствующие регистры порта установлены в 0)
ENCODER_PORT |= ((ENCODER_LEFT)|(ENCODER_RIGHT));
}

encoder.h
CODE

#ifndef ENCODER_H
#define ENCODER_H

//Конфигурация порта энкодера
#define ENCODER_PORT PORTC
#define ENCODER_REG DDRC
#define ENCODER_PIN PINC

//Куда подключен левый вывод, куда - правый
#define ENCODER_LEFT (1<<4)
#define ENCODER_RIGHT (1<<3)

//Состояние покоя энкодера
#define ENCODER_NONE 0

//Считываем состояние энкодера
#define READ_ENCODER_STATE() ((~ENCODER_PIN) & ((ENCODER_LEFT)|(ENCODER_RIGHT)))

//Переменная для обработки состояний порта в прерывании
uint8_t ENCODER_TURN;

extern uint8_t ENCODER_TURNED;
extern volatile int8_t ENCODER_STATE;
extern volatile uint8_t isEncoderTurned;
extern volatile uint8_t ENCODER_DEBOUNCE_COUNTER;

void ENCODER_TASK(void);
int8_t encoder_state(void);
void encoder_init(void);

#endif

Ну и в прерывании кусок:
CODE

ENCODER_DEBOUNCE_COUNTER++;
if (!ENCODER_TURNED)
{
ENCODER_PORT &= ~(0x04<<ENCODER_TURN); //Выставляем 0 на вывод порта энкодера по очереди
if (++ENCODER_TURN > 1) //Переходим на следующий вывод порта. Если достигли конца,
ENCODER_TURN = 0; //начинаем считать с начала
ENCODER_PORT |= (0x04<<ENCODER_TURN); //Возвращаем 1 на соответствующий вывод порта энкодера
asm("nop");
ENCODER_STATE = READ_ENCODER_STATE(); //Считываем состояние порта энкодера
if (ENCODER_NONE != READ_ENCODER_STATE()) //Проверяем, действительно ли мы повернули энкодер
ENCODER_TURNED = 1; //Таки повернули
} else //И мы всё ещё поворачиваем его
ENCODER_TASK();
Alex11
Я не понял, зачем Вы дергаете пинами на вывод. Как мне кажется, они должны стоять на вход, считываете одно из четырех состояний, дальше должен быть сделан автомат, помнящий предыдущее состояние и предыдущее направление вращения (всего 8 состояний). Дальше переходы из одного состояния в другое в зависимости от входного.
GoodNews
Скажем пока вышел из положения изменением значений счётчика ENCODER_DEBOUNCE_COUNTER с 20 и 80 на 5 и 55 соответственно. Однако мне кажется тут можно красивее сделать...

Цитата(Alex11 @ Feb 26 2010, 18:36) *
Я не понял, зачем Вы дергаете пинами на вывод. Как мне кажется, они должны стоять на вход, считываете одно из четырех состояний, дальше должен быть сделан автомат, помнящий предыдущее состояние и предыдущее направление вращения (всего 8 состояний). Дальше переходы из одного состояния в другое в зависимости от входного.

Они не на выход, а на вход стоят. Регистр порта выставлен на вход с подтяжкой.
Код
ENCODER_REG &= ~((ENCODER_LEFT)|(ENCODER_RIGHT));
DpInRock
По фронту одного сигнала считываю состояние другого.
Дребез в голову не беру.
На контактах - емкости.

Влево - увеличиваю счетчик ЛЕВО.
Право - увеличиваю счетчик ВПРАВО.
Как токо третий бит вздымается хоть у одного - сбрасываю оба счетчика и выдаю соответсвующий сигнал.
GoodNews
DpInRock
Через аналоговый компаратор?
У меня энкодер средним выводом подключен на землю, а крайние - на порт. Пока в том варианте работает. Там может что другое попробую. По крайней мере ваше предложение с обработкой дребезга пригодилось дважды a14.gif .
DpInRock
Какой еще компаратор? Просто на порт. Один из портов - прерывание по фронту.
В реальной жизни, если дребезга много, прерывания лучше не использовать, или использовать, но тогда уж на обоих портах. Прерывание на А разрешает прерывание на Б и запрещает само себя. Б - разрешает А и запрещает само себя. Где-то так.
PhX
Тема подымалась множество раз.
Попробуйте почитать здесь.
GoodNews
PhX
Интересный вариант. Спасибо. Я так понимаю, в таком варианте получается лог.1 либо на Up, либо на Down.
Я ещё вот думаю последовать совету DpInRock и повесть пару конденсаторов для удержания лог.уровня на пинах.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.