Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

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

С их помощью очень удобно организовывать меню. Мне известны случаи, когда на очень серьезном и дорогом устройстве, все управление организовано, при помощи всего одного энкодера. Аналогично, в давние времена попадалась модель телефона, где все управление, также было организовано всего одним колесиком.

Прежде всего энкодеры бывают нескольких типов, рассматриваемый в данной статье —  механический инкрементальный.  В качестве испытуемого, был использован pec12-4220f-s0024. Внешне он похож на переменный резистор, но, в отличие от резистора, он не имеет ограничителей, т.е. может крутиться бесконечно в любую сторону.

15-1

Результат работы такого устройства — двоичный код Грея. Получить его можно анализируя состояние ножек, на которые приходят импульсы от энкодера.

15-6

Теперь рассмотрим все более детально. Электрически он представляет собой 2 кнопки без фиксации, когда мы начинаем крутить они по очереди срабатывают — сначала одна, затем вторая. В зависимости от того, в какую сторону мы вращаем, одна из кнопок срабатывает раньше или позднее. Для того чтобы узнать, в каком состоянии находятся эти кнопки, ножки порта (к которому подсоединен энкодер) должны быть подтянуты к «+» питания.

15-4

На разобранном энкодере 1/3 площадки относится к 1 контакту, 1/3 к 2 контакту, сплошной участок — общий. Когда скользящие контакты попадают на изолированные участки (черные), слышны щелчки. В этот момент энкодер, находится в устойчивом состоянии, когда обе кнопки разомкнуты. На ножках порта будут лог единицы(состояние 11).

15-5

Как только мы начинаем вращать в какую либо сторону, один из контактов замыкается на землю. На этой ножеке появится лог 0, на второй ножке по прежнему будет лог1 (состояние 01). Если мы продолжаем вращать, на второй ножке появится лог0(состояние 00). Далее, на первой ножке пропадает контакт (состояние 10), в конце концов энкодер возвращается в устойчивое состояние (11). Т.е. на один щелчок приходится 4 изменения состояния. Временная диаграмма выглядит так:

15-7

При вращении в противоположную сторону, идея остается прежней, только сначала будет замыкаться, вторая ножка.

Если выписать эти состояния в двоичной системе и перевести их в десятичную, то получится следующий порядок(для вращения в одну сторону):
11=3
01=1
00=0
10=2

При вращении в противоположную сторону:
11=3
10=2
00=0
01=1

Теперь осталось понять, как эти значение обрабатывать. Допустим, энкодер подключен к ножкам порта В0 и  В1. Нам нужно прочитать эти ножки. Есть довольно хитрый способ, но для начала нам нужно понять операцию «логического и» (&).

Результат будет равен единице, только если оба числа равны 1, т.е. результат операции 1&1, будет равен 1. Следовательно 1&0=0, 0&0=0, 0&1=0.

Логическое & поможет нам вычленить из целого порта, только интересующие нас ножки. Т.е. операция x=0b00000011 & PINB; позволит нам прочитать в переменную «х» состояние первых двух ножек, независимо от того, что находится на остальных ножках. Число 0b00000011 можно перевести в шестнадцатеричную систему 0х3.

Теперь все необходимые знания для написания прошивки у нас есть. Задача: увеличивать/уменьшать переменную Vol, при помощи энкодера, результат вывести на lcd дисплей.

#include <mega8.h>
int NewState,OldState,Vol,upState,downState;
#asm
   .equ __lcd_port=0x12 ;PORTD
#endasm
#include <lcd.h>
#include <stdio.h>
 
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{ 
NewState=PINB & 0b00000011;
if(NewState!=OldState)
{
switch(OldState)
	{
	case 2:
		{
		if(NewState == 3) upState++;
		if(NewState == 0) downState++; 
		break;
		}
 
	case 0:
		{
		if(NewState == 2) upState++;
		if(NewState == 1) downState++; 
		break;
		}
	case 1:
		{
		if(NewState == 0) upState++;
		if(NewState == 3) downState++; 
		break;
		}
	case 3:
		{
		if(NewState == 1) upState++;
		if(NewState == 2) downState++; 
		break;
		}
	}            
OldState=NewState;
}
TCNT1H=0x00;
TCNT1L=0x00;
}
 
void main(void)
{  
char lcd_buf[17];
 
// Input/Output Ports initialization
// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=P State0=P 
PORTB=0x03;
DDRB=0x00;
 
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
// Mode: CTC top=OCR1A
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x0A;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x03;
OCR1AL=0xE8;
OCR1BH=0x00;
OCR1BL=0x00;
 
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x10;
 
// Global enable interrupts
#asm("sei")
lcd_init(8);
 
while (1)
      { 
      if (upState >= 4) 
      {                            
        Vol++;
        upState = 0;
      }
      if (downState >= 4) 
      {                              
        Vol--;
        downState = 0;
      }
      sprintf(lcd_buf,"vol=%d",Vol);
      lcd_gotoxy(0,0);
      lcd_clear();
      lcd_puts(lcd_buf);
 
      };
}

В качестве пояснений: таймер 1 настроен на срабатывание 1000 раз в секунду, строкой NewState=PINB & 0b00000011; считываем состояние ножек 0 и 1 портаВ. if(NewState!=OldState) если состояние не изменилось, значит вращения нет.
Если состояние изменилось конструкция switch определяет в какую сторону было произведено вращение, в зависимости от этого увеличивается значение переменной downState(влево) или upState(вправо).

От щелчка до следующего щелчка 4 изменения состояния, поэтому 1 раз за 4 импульса изменяем переменную Vol. Ее же и выводим на дисплей. Прошивка доступна здесь

26 комментариев: Урок 15.1 Управление инкрементальным энкодером при помощи AVR.

  • если использовать инструкцию А=PIND обязательно все выводы объявлять как ВВОДы
    или же в остальные биты запишутся нули

  • не обязательно

  • у меня энкодер работает без прерываний. На один вход Атмеги через электролит подается с одного контакта, (и на этот же контакт энкодера +5в через 4,7к)-и после элека на Атмегу тоже 4,7к-смысл в том что в каком положении энкодер не остановишь на Атмеге все равно поднимется 1, а второй контакт энкодера на вторую ногу Атмеги и 4,7к на +5в. в программе пишем если(на 1 ноге Атмеги 0 ) то проверить что на второй
    если 1- то вращение в перед, а если 0 то вращение на зад. преимущества-не задействованы прерывания, Недостаток- нельзя крутить очень быстро.

  • А с какого перелягу незадейсвованные прерывания это преимущество? Или загрузить контроллер по самые помидоры это норма? Как раз прерывания это и есть преимущество. Так как контроллер может выполнять параллельно задачу, пока не отработало прерывание.

  • Я часто использую способ, когда по таймеру, с определенной периодичностью, в основном цикле опрашивается сразу несколько устройств, при этом задержки в основном цикле не использую. При этом хорошо отсекаются всякие дребезги. Возможно где то прерывания могут оказаться лишними, бывает и так. Зато везде где есть емкости, там есть и броски напряжения. Поэтому, вопрос конкретной реализации и личных предпочтений.

  • Там 1 000 раз в секунду или 1 000 000 раз в секунду? Если я правильно понимаю, там 1000 килоГерц, что равно 1 мегаГерцу. И делитель вроде как 8 стоит же…

  • Извиняюсь, понял: проморгал что Compare A = 03E8 = 1000

  • Интересная статья. познавательная, по поводу прерываний полностью согласен с автором, хотя, лично я, скорее всего, применил бы прерывание по внешнему сигналу INT (правда для этого придется энкодер повесить на соответствующие ножки, что не всегда удобно…)

  • Протестировал на Atmega16. Все получилось с первого раза.Спасибо.

  • Уважаемы admin подскажите у меня у энкодера вот такой алгоритм:
    в одну сторону: 11 00 01 11 00 01 11
    в другую сторону:11 01 00 11 01 00 11
    Не могу нормально настроить, получается только в одну сторону. Может энкодер поломан или это такая разновидность?

  • Все разобрался, вскрыть пришлось, у меня общий не по центру был, а крайний!

  • Скажите, как в этом примере добится того что бы не было мерцания дисплея, очень ощутимое мерцание, что даже цифра полностью прорисоватся не успевает. Мк прошит от внешнего кварца на 8герц.

  • затирать только нужные символы, или реже очищать

  • Здравствуй, все сделал работает, решил перевесить на порт Д6 и Д7 выводы, изменил DDRD на входы, PORTD притянул и изменил тут NewState=PIND & 0b11000000; и не работает не могу понять почему

  • чудес не бывает 🙂 проверяйте что не так

  • interrupt [TIM2_COMP] void timer2_comp_isr(void)
    {
    NewState=PINC & 0b00110000;

    if(NewState!=OldState)
    {
    switch(OldState)
    {
    case 2:
    {
    if(NewState == 3) upState++;
    if(NewState == 0) downState++;
    break;
    }

    case 0:
    {
    if(NewState == 2) upState++;
    if(NewState == 1) downState++;
    break;
    }
    case 1:
    {
    if(NewState == 0) upState++;
    if(NewState == 3) downState++;
    break;
    }
    case 3:
    {
    if(NewState == 1) upState++;
    if(NewState == 2) downState++;
    break;
    }
    }
    OldState=NewState;
    }

    TCNT2=0x00;

    }

    ASSR=0<<AS2;
    TCCR2=(0<<PWM2) | (0<<COM21) | (0<<COM20) | (0<<CTC2) | (0<<CS22) | (0<<CS21) | (1<<CS20);
    TCNT2=0x00;
    OCR2=0xFF;
    TIMSK=(1<<OCIE2) | (0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (0<<TOIE0);

    помоги разобраться? не хочет работать на этом порту. где ошибка может

  • Результат надо сдвигать, т.е. после этого NewState=PINC & 0b00110000; NewState нужно сдвинуть вправо, чтобы получились 0, 1, 2, 3

  • Здравствуйте!

    Замечательный рабочий пример работы с энкодером.
    Я единственное усовершенствовал — при включении устройства, значения OldState и NewState должны быть равны 3, иначе сдвиг энкодера на одну позицию после включения программой не улавливается, очень раздражало (получалось 0 — 2 — 0 — 1 — 3), сейчас же после включения первый сдвиг энкодера в любую сторону улавливается. Так же я использовал PCINT, так как таймеры заняты другим.

  • 😯 1000 раз в секунду 😕 если программа только для энкодера, то такой код подойдет 🙁

  • Здравствуйте! Не могли бы Вы объяснить алгоритм работы switch в данном примере? Понятно что от щелчка до щелчка энкодера проходит 4 смены состояния. Возьмём 3-2-0-1. При подаче питания NewState примет значение 3, правильно? Далее при повороте энкодера значение изменится на 2. NewState станет не равно OldState и подключиться switch. Далее немного не понял, как и в каком порядке происходит сравнение ? В какой кейс запрыгнет программа сначала? Что будет дальше, если будет (не будет) совпадений? И почему кейсы записаны не по порядку? Если можно опишите пошагово что будет выполнять программа при каждом изменении значения NewState, интересует только как Switch определяет направление вращения энкодера, остальное вроде все понятно. Спасибо!

  • Вроде дошло. В зависимости от того, какое значение принимает OldState тот кейс и будет выполнятся. Тогда нужно при включении OldState присвоить значение 3, иначе первый щелчек энкодера не будет обрабатываться. Значит при первом изменении значения NewState, выполнится кейс 3, так как значение OldState будет равняется 3. В зависимости от направления вращения (например 1) увеличится значение upState. Далее по коду OldState примет значение 1, значит следующий выполнится кейс 1 и так далее, верно?

  • привет у меня все работает ,даже пины менял

  • Всем привет ,Использую данный проект в оптическом энкодере на 3600 результат плачевный при медленном кручении нормально ,а вот быстро не успевает ,выход энкодера полумост на двух транзисторах .Что делать может с таймером поиграть не успевает он обработать?

  • у меня вопрос.У stm32 есть настройка в таймере энкодер.Алгоритм подойдёт этот.И если в прерывании по таймеру.Значит ещё один таймер нужно брать.?Хочу сделать настройку энкодером.Но проблема в том.Что настройка предыдущего параметра остаётся ?

  • Да и ещё.Чем отличается аппаратный энкодер у stm32.От программного?

  • В симуляторе протеус получилось.А в железе не пробовал ещё.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика