Я тут сидел ковырял будущую прошивку для усилителя и понял, что такой процент ошибочного определения направления вращения энкодера меня совсем не устраивает. Поэтому было решено внедрить таки процедуру, основанную на опросе состояний.
Приведу картинки из прошлой статьи:
Если на них внимательно посмотреть, то можно определить, что в зависимости от вращения состояния A и C образуют цифры.
Для одного направления : 0 1 3 2 0 1 3 2 …
Для другого : 0 2 3 1 0 2 3 1 0 …
Таким образом, сравнивая текущее состояние двух линий с предыдущим мы можем определять направление вращеня. Осталось только завести таймер на опрос. Чтобы не было прострелов можно установить время опроса поменьше, я так и поступил – у меня опрос через каждую милисекунду.
Вот что примерно вышло:
#include <htc.h> #include <stdio.h> #include "usart.h" #define _XTAL_FREQ 4000000 #define left RB1 // пин энкодера А #define right RB2 // пин энкодера С volatile unsigned char EncData; //глобальная переменная для сохранения текущего состояния линий энкодера __CONFIG(WDTDIS & UNPROTECT & LVPDIS & HS); void main() { unsigned char OldEncData = 3; //сохраням старое значение линий, инициализируем с 3 unsigned char upcount = 0; unsigned char downcount = 0; TRISB1 = 1; TRISB2 = 1; init_comms(); //инициализируем уарт для дебага OPTION = 0b11010001; //настраиваем таймер 0, на 1 мс TMR0 = 0; T0IE = 1; GIE = 1; while (1) { if (OldEncData != EncData) { //если новое значение отличается от старого switch (OldEncData) { case 0 : if (EncData == 1) {upcount++; downcount=0; } if (EncData == 2) {downcount++; upcount = 0; } break; case 1 : if (EncData == 3) {upcount++; downcount=0; } if (EncData == 0) {downcount++; upcount = 0; } break; case 2 : if (EncData == 0) {upcount++; downcount=0; } if (EncData == 3) {downcount++; upcount = 0; } break; case 3 : if (EncData == 2) {upcount++; downcount=0; } if (EncData == 1) {downcount++; upcount = 0; } break; } OldEncData = EncData; //текущее значение = старое значение } if (upcount >= 4) { //флаг поворота направо printf("\r\n UP"); upcount = 0; } if (downcount >= 4 ) { //флаг поворота налево printf("\r\n Down"); downcount = 0; } } } interrupt isr() { if (T0IF) { TMR0 = 0; EncData = PORTB & 0b00000110; EncData >>= 1; T0IF = 0; } }
Сначала я пытался ловить флаг по увеличению переменных upcount или downcount на 1. Однако тестовое сообщение вываливалось 4 раза, за один поворот, естественно я решил ждать пока переменная не будет равна 4.
Результат работы этого кода меня просто поразил. !Ни одного! прострела, все работает идеально, даже на моем старом разболтанном энкодере.
Пытаюсь сделать отработку энкодера на основании вашего проекта на PIC16F873A. кварц 8МГц. В протеусе худо бедно работает, а в железке нет. Как будто из-за дребезга. Если быстро крутить энкодер вообще реакции ни какой если медленно иногда очень редко срабатывает. Где косяк не пойму. Вот код делаю в MIKROC
Брр, тяжело разбираться во всем коде, я бы порекомендовал сделать следующее:
1. Создать проект, в котором будет только энкодер (чтобы я мог помочь желательно на hi-tech, я на микрос пару простых программ только делал) и количество вращений выводилось бы по уарту в терминал, так проще будет и отлаживать проект и мне не надо будет весь код понимать.
2. Очень желательно найти осциллок или логический анализатор и посмотреть если резко крутануть энкодер, сколько импульсов вылетит, на основании этого прикидывать время опроса энкодера.
Это вольтметр и амперметр. Для измерения тока исполбзовал два канала АЦП один на мА второй на А с автоматическим переключением каналов. Также выводятся мощность и сопротивление нагрузки. Энкодер управляет ШИМ и еще сделал просто чтобы на экране видеть как цифры меняются. переключение по нажатию кнопки. Переписывать все HI-TECH не сильно хорошоя идея для меня. Часть кода уже сделана да и в програмировании я новичок. Объясните пожалуста как получается 1мс по моим расчетам 1мкс OPTION = 0b11010001; //настраиваем таймер 0, на 1 мс.
Частота кварца 4МГц делитель 001 1:4 получается 1МГц Т=1/100000=0,000001с или 1мкс.
Для кварца 8МГц я по аналогии выбрал делитель 1:8. Даже в протеусе если поставить мотор-энкодер процесор почти не реагирует на вращения энкодера. Иногда выскочит 1 или 2 а потом начинает уменьшаться хотя направление вращения не меняю. Похоже получается и в железе.
По таймеру: вы не совсем правильно считаете:
1. Частота кварца 4 МГц, после предделителя 1/4 = 1 МГц ~ 1мкс
2. Дальше настраиваемый предделитель, который у меня делит на 4 ~ 4 мкс
3. Прерывание по таймеру TMR0 происходит в тот момент, когда он натикает 256 раз, то есть на момент срабатывания таймера временной промежуток будет равен 256 * 4мкс = 1024 мкс ~ 1 мс
Подробнее про настройку таймера можно почитать вот тут http://diymicro.ru/pic-mk-eksperiment-3-ispolzovanie-tajmera-tmr0.html
По коду, повторюсь: сделайте аналог моего проекта, отладите сначала энкодер, а потом внедрите в существующий проект, так отлаживать гораздо проще и мне не надо будет разбираться полностью во всем (если конечно не заработает, но мне кажется что будет работать :))
PS. Энкодер лучше сразу в железе отлаживать.
Попробовал только оставить процедуру энкодера в MICROC вроде работает но если укрутить быстрее одни пропуски. Попробовал кварц на 16МГц стало почти нормально, но если бывтро крутить энкодер немного пропускает. Решил все в хайтеке попробовать не могу подключить lcd.h. Т.е. пока в программе нет lcd_init(); все компилируется стоит добавить этот файл ступор “HI-TECH C Compiler for PIC10/12/16 MCUs (PRO Mode) V9.71a
Copyright (C) 2010 Microchip Technology Inc.
Serial number: HCPICP-654321 (PRO)
Error [499] ; 0. undefined symbol:
_init_lcd(bp2.obj) ”
Хайтек только осваиваю почему не работает хотя init_comms(); проходит. В шапке добавил #include “lcd.h” файлы lcd.h и lcd.c бросил в папку с проектом. В lcd.h только пины на свои поменял.
Если менять кварц и становится нормально – значит у этого энкодера другие временные интервалы => подлкючать к осцилку/логическому анализатору и определять нужный интервал.
Откуда брался файл lcd.h? Случайно не из сайта у меня содержимое копировалось? Там какой то косяк с этим, надо почему то руками именно этот файл перенабрать, я тоже с таким сталкивался.
Может какие скрытые символы вставляются или еще что – фиг его знает.
Пробовал с сайта копировать и с проекта кажется тоже брал уже запутался. А с энкодером фиг его знает на другом проекте работал нормально не моем правда. Осцилограф С1-68 есть но однолучевой если он не отсырел в подвале попробую посмотреть какая длительность сигнала получается. Уже давно не включал его.
lcd.h попробовал с вашего проекта взять ничего не меняя таже ошибка
Набрал вручную тоже самое.
Ну скиньте ваш проект архивом, приду с работы домой посмотрю.
http://www.fayloobmennik.net/1851264
Так епрст, вы естественно будет ошибка, вы вызываете функцию init_lcd(), которой в природе нет :). В файлах определена функция lcd_init().
Все нормально компилируется.
Да похоже ступил!?. Дописал вывод на дисплей переменной из функции энкодера и проверил на вшивость. Работает отлично и на 4МГц. Мне от думается, что в MICROC что то с прерываниями пошло не так. Я использовал для ШИМ и АЦП готовые функции, а они все с прерываниями работают вот там наверное что то и не срослось. Ну буду пробовать дальше делать в хайтеке правда это отбросит меня назад пока врублюсь как АЦП, ШИМ замоторить фунции вывода на дисплей переделать ну и много всего тому подобного.
Похоже рано обрадовался как только добавил три канала АЦП и два ШИМа. Вся красота работы энкодера сразу улетучилась. Если быстро крутить не реагирует если медленно с пропусками.
Попробуйте выключить все прерывания во время обработки энкодера, то есть примерно такой алгоритм:
Прерывание по таймеру TMR0;
Выключаем все прерывания (GIE = 0);
Возимся с чемто нужным для нас;
Обнуляем TMRIF, Включаем прерывания (GIE = 1).
Ну это первое что на ум приходит. Не получится, давайте опять архив с проектом, но если честно без железа не представляю как смогу помочь.
PS. У меня в будущих мозгах для усилка, вместе с энкодером заюзаны все таймеры + прерывания по измению состояния пинов и все вроде как работает.
Может еще раз посмотрие http://www.fayloobmennik.net/1860940
Охохо, что ж вы не хотите поблочно все отлаживать, а нахрапом все…
Первое, что нужно сделать – избавиться от всего этого шлака в обработчике прерываний, в нем не рекомендуется вызывать другие функции.
Не выйдет – тогда оставляем один канал АЦП или же один шим, а не все сразу.
Вот первоначальный вариант. Здесь на экран все выводиться, но энкодер кульгает. Это даже в протеусе заметно http://www.fayloobmennik.net/1861060
Блин, нельзя в протеусе отлаживать такие проекты, это потеря времени, причем уже не только вашего.
Без обвеса да, в протеусе все вроде как работает, но как только вы нагородили огород, время выполнения как можно заметить увеличилось (строка внизу).
В любом случае, на данный момент проект имеет смысл отлаживать только в железе!
Да и еще, зачем постоянно в цикле обновлять какие-то значения на экране и при этом ловить энкодер, какой в этом смысл? Изменять ШИМ? Может лучше сделать это отдельной опцией меню?
Зашли в нужный пункт, изменили скважность, вернулись в функцию отображения.
А то ресурсы непонятно куда расходуются. Ну и так далее, много нюансов. Такие проекты надо писать под знакомый, иначе получается такая каша.
Ясно понял спасибо.
а как вам такой код, у меня работает без глюков
enum { ES0, ES1, ES3, ES2 }; //encoder sequential states
void pollEncoder(void);
void Encoder_Init(void);
static char EncPre; //предыдущее состояние энкодера
static char EncPpe; //пред-предыдущее состояние энкодера
char Enc_Scan(void);
void main()
{
Encoder_Init();
while(1)
{
pollEncoder();
}
}
void pollEncoder(void)
{
char EncTmp = Enc_Scan(); //сканируем энкодер и запоминаем результат
if (EncTmp == EncPre) return; //если нет изменений, выход
__delay_us(255); //антидребезговая задержка для энкодера
char EncNew = Enc_Scan(); //сканируем энкодер еще раз
if (EncNew != EncTmp) return; //дребезг не закончился, выход
EncTmp = EncPpe; //пред-предыдущее состояние
EncPpe = EncPre; //предыдущее состояние
EncPre = EncNew; //новое состояние
if (EncNew == ES2 && EncPpe == ES1 && EncTmp == ES0)
{ volume_down = 1; count=0; return; } //вращение против часовой стрелки
if (EncNew == ES2 && EncPpe == ES3 && EncTmp == ES0)
{ volume_up = 1; count=0; return; } //вращение по часовой стрелке
}
void Encoder_Init(void)
{
EncPre = Enc_Scan(); //сканируем энкодер и запоминаем состояние
EncPpe = EncPre; //запоминаем пред-предыдущее состояние
}
char Enc_Scan(void)
{
char n = 0;
if(ENC_A) n |= 0x01; //проверка линии F1
if(ENC_B) n |= 0x02; //проверка линии F2
return(n);
}
p.s. не много ли в нем воды….. может как почистить
Ну я тоже не большой эксперт, но пару моментов могу подсказать:
1. Функция Encoder_init на мой взгляд лишняя, т.к. в ней фактически просто присваивание одной переменной.
2. Ээ, а чего вы добиваетесь выражением EncPre = EncPre? (это тоже самое, что написать 10 = 10).
3. Функция poll_encoder, вот тут я вообще не понимаю как оно может работать, в ней вы делаете так:
EncPre = EncNew;
а потом:
if (EncNew == ES2 && EncPpe == ES1 && EncTmp == ES0)
{ volume_down = 1; count=0; return; } //вращение против часовой стрелки
if (EncNew == ES2 && EncPpe == ES3 && EncTmp == ES0)
{ volume_up = 1; count=0; return; } //вращение по часовой стрелке
То есть фактически EncPre всегда равно EncNew! По идее в эти циклы вообще входа нет, как у вас переменные volume обновляются я не понимаю.
4. Ну и я бы это делал на прерывании по таймеру, а не макросом __delay_us, т.к. когда код разрастется код может работать не так как вам хочется.
код не мой… там идея такая….. что типа выполняется контроль скорости вращения… вы попробуйте у себя в железе воплотить, у меня срабатывает идеально…
без Encoder_init я тоже пробовал… работает
и я так понимаю там идет контроль 3-х разных состояний EncPpe, EncPre, EncNew!
на “count=0” не смотрите… это фича) от маленького счетчика загорания и погасания подстветки экранчика при активности на портах
Как вот это может работать?
EncPre = EncNew; //новое состояние
if (EncNew == ES2 && EncPpe == ES1 && EncTmp == ES0)
{ volume_down = 1; count=0; return; } //вращение против часовой стрелки
if (EncNew == ES2 && EncPpe == ES3 && EncTmp == ES0)
{ volume_up = 1; count=0; return; } //вращение по часовой стрелке
Сами посудите, EncPre=EncNew, а переменные обновляются только если их значения различны.
Как вы определяете, что у вас “все работает”? Можно полный текст проекта? И ссылку на первоисточник еще если можно.
PS. Я не утверждаю, что весь код нерабочий, вращение обрабатывается, но вот переменная таким образом ну никак не может обновляться.
проверял на счетчике который выводил это на LCD
вот первоисточник
http://www.530.ru/wwwboards/mcontrol/2143/messages/600755.shtml
ну это не совсем та версия кода…. но из того же форума
Ептыть только после этого форума понял что здесь есть две переменных EncPpe и EncPre
Ну нафиг так переменные обзывать 🙂 тогда все логично. Но на самом деле код практически такой же как и мой, за исключением юзания таймера, а так суть одна и та же, реализации разные.
яж говорил 3 разных состояния и переменных)
форум мутный…. тяжело понять где там файнал версия)
Ну это совсем не прозрачно, я ведь и спросил какой смысл в выражении
EncPre = EncPre
а там оказывается EncPpe – жесть в общем, и ведь несколько раз внимательно код перечитывал…
Автору ещё раз РЕСПЕКТ!! Работает даже на TMR2!! Работает ОТЛИЧНО!!!
Попробовал код в железе, все работает отлично. Но не могу понять почему идет отсчет увеличеия или уменьшения переменной только через два щелчка энкодера?!
Видимо у вас более быстрый энокдер, уменьшите переменный upcount и downcount до 2, по идее все должно заработать.
Вот код слегка переделанный под себя, для макетки. Энкодер тоже как и у вас с кнопкой кажется на 30 позиций. В четных позициях 11, в нечетных 00. А остальное генерится пока идет переход с позиции на позицию.
void main()
{
while(TRUE)
{
pollEncoder();
}
}
char Enc_Scan(void)
{
char n = (input_a() & 48);
n = (n>>4);
return(n);
}
void pollEncoder(void)
{
char EncTmp = Enc_Scan(); //сканируем энкодер и запоминаем результат
if (EncTmp == EncPre) return; //если нет изменений, выход
delay_us(255); //антидребезговая задержка для энкодера
char EncNew = Enc_Scan(); //сканируем энкодер еще раз
if (EncNew != EncTmp) return; //дребезг не закончился, выход
EncTmp = EncPpe; //пред-предыдущее состояние
EncPpe = EncPre; //предыдущее состояние
EncPre = EncNew; //новое состояние
if (EncNew == 3 && EncPpe == 1 && EncTmp == 0)
{upcount = 1; } //вращение против часовой стрелки
if (EncNew == 2 && EncPpe == 0 && EncTmp == 1)
{ downcount = 1; } //вращение по часовой стрелке
if (upcount) { //
h++;
if (h>=63){h=63;} //заплняем 6бит порта с 16F630 и остаемся в максимуме
output_C(h);
upcount = 0;
}
if (downcount ) {
h–;
if (h > 63){h=0;} //Без этой строчки при первом включении крутим в минус а на
//порту C все вот так 0b00111111
if (h == 255){h=0;}
output_C(h);
downcount = 0;
}
}
будет нагляднее если код в теги
поместите
Dobroe vremya sutok . hotel razobratsya encoderom . poproboval vashu programmu peredelat na 12f675,
encoder podkluchil na vhodi GP3 i GP5. perepisal v prerivanie ,eto pravilno ? pochemuto ne hochet rabotat
interrupt isr() {
if (T0IF) {
TMR0 = 0;
EncData = GPIO & 0b00101000;
EncData >>= 1;
T0IF = 0;
}
Я бы сначала проверил отрабатывает ли обычная кнопка на ваших этих пинах.
proboval ; pri vrashenie encodera svelodiodi migayut . v vashem kode neispolsovani left i right ,mojet bit eto prichinoy ne raboti?
if(!left)GP1=1;
else GP1=0;
if(!right)GP0=1;
else GP0=0;
блин только сейчас заметил:
У вас так не прокатит, надо менять эту часть (если принципиально использовать данные пины):
dobriy den . mojete obyasnit eti stroki & i gt novie peremennie?
EncData_temp0 = (GPIO & 0b00101000)>> 3;
EncData_temp1 = (GPIO & 0b00101000)>> 4;
Посмотрите сейчас внимательно на свое сообщение и поймете что это за переменные 🙂
sorry .kak to stranno do otpravleniya tam bili nadpisi ;
EncData_temp0 = (GPIO& 0b00101000)>> 3;
OGROMNOE VAM SPASIBO ZARABOTALO , YA NA VIHODI POSTAVIL DVA SVETODIODA , pri vrashenii encodera v odnu storonu zagoryetsya odin svetodiod . a pri vrashenii v drugom napravlenii zagoritsya drugoy. i ya zametil tolko posle 4 shelchka encodera zagoraetsa svetodiodi , rabotaet programma tolko pri sdvige na 4 shelchka .
Да у меня тоже было 4 щелчка, там есть специальные переменные для этого в моем коде.
perepisal peremennuyu 4 na 1 , teper srabativaet s odnogo shelchka bez oshibok , blogodaren vam za horoshuyu statyu i za horoshiy sayt dlya novichkov , i za podderjku.
Пытаюсь приладить Ваш код с таймером на pic12f629, пока не получилось, поэтому попробовал тот, что в комментарии без таймера. Работает на половину, т.е. при вращении вправо один светодиод горит, влево – нет. Если менять контакты А и В местами, то работает влево, вправо – нет, т.е. ситуация меняется на обратную. Еще момент: как подключать ножки энкодера? Одну из ног притянул к минусу резистором 1 кОм. На “общий” энкодера подаю плюс, кнопку пока не использую.
Код ниже:
Для начала не углубляясь в код (тем более что это не мой код), судя по вашему сообщению у вас подключена только одна ножка энкодера, это так?
Мое подключение описано в прошлой статье – вот картинка http://diymicro.ru/wp-content/uploads/2012/01/wiring.jpg.pagespeed.ce.iwpSdAA2Bk.jpg.
Ну и с одной ножкой подключенной вряд ли будет правильно работать.
Да, Вы были правы. Подключил резистор и заработало. Теперь попробую Ваш код, ножки будут подключены на GP4 и GP5.
Вот этот код мне не совсем понятен, можете объяснить?
EncData_temp0 = (GPIO & 0b00101000)>> 3;
EncData_temp1 = (GPIO & 0b00101000)>> 4;
В моем случае (GP4 и GP5) будет 0b00110000?
Да, и сдвиг надо делать >>4 и >>5, в прошлом коде видимо ошибка закралась в комментариях.
Все работает!
Спасибо Вам за оперативный отзыв и за замечательное содержание сайта!
Еще вопрос: стоит ли на кнопку вещать двойной клик? Сами не пробовали, например, один клик вкл/выкл, двойной – меню или что-то другое? И как лучше это реализовать?
Не пробовал вроде, но не вижу особых проблем, нужно просто с таймингами хорошенько поиграться, чтобы ложных срабатываний не было.
Я бы делал наверное как то так:
Мониторим прерывание – ждем антидребезг + задержка на первое отпускание – проверяем кнопка отпущена? – если да, новый таймер для прерывания второго нажатия, если по истечении времени не была нажата второй раз значит было одинарное нажатие, была нажата, значит двойной клик.
Уточнение. При вращении, допустим, вправо, горит зеленый светодиод. При вращении влево зеленый гаснет, но красный не загорается. Для проверки в начале программы до вращения энкодера красный включил, т.е. ножка работает.
Уведомление: Усилитель с мозгами на tda7294 | PIC микроконтроллеры
Уведомление: Pic Lab, PIC16, Experiment #20.1, The encoder, the polling states method | diymicro.org