|
Алгоритм для надійного зчитування показань енкодера кута повороту для ArduinoSAGE В процесі експериментів з аналогом плати Arduino Uno з'ясувалося, що немає досить надійного коду для читання показань механічного енкодера кута повороту, витягнутого з мишки. Представлено лише вагон і маленька візок полурабочие прикладів, працюючих вкрай нестабільно. Довелося усунути цю прикру неприємність і написати цей код. Проблема взаємодії з механічним енкодером полягає в тому, що треба ефективно вирішити проблему брязкоту контактів, яка властива цього пристрою, так ж як і кнопкку. Для кнопки досить влаштувати затримку в кілька мілісекунд. Для енкодера ж треба аналізувати його стан з високої частотою, і затримки тут відразу знизять ефективність. В прикладах досить недвозначно дають зрозуміти, що більше ефективний алгоритм дожен використовувати переривання. Чим я і скористався. Читання стану виходів енкодера здійснюється 2-мя переривань по зміни рівня. Кожне подія переривання забезпечується унікальним ідентифікатором розрядністю в 1 байт, чого цілком досить, якщо в основному циклі Arduino аналізувати надійшли події без додаткових затримок. Для ідентифікації використовується однобайтовое число з знаком (short). Постійний інкремент його значення природно дає переповнення, але в цьому немає нічого страшного якщо знати як з такими числами правильно працювати. В Зокрема, Не дивлячись на переповнення, ми можемо ввести для кожного з двох чисел получающейся послідовності, якщо вони Не занадто видалені друг від друга, таку операцію як відстань між двома такими числами (функція EventsBetween). Через кожну 1 мс здійснюється аналіз надійшли подій за цей період. Аналізується кількість подій (використовуємо EventsBetween) і спеціальна сума, якадопомагає визначити, який рівень вхідного сигналу в даних події переважав. Будь-яке подія зниження рівня входить в суму як доданок, рівне -1, а всяке подія підвищення рівня як доданок, рівне +1. Таким чином, якщо сума вийшла негативною найімовірніше завсе сталося зниження рівня, якщо сума позитивна - скоріше завсе сталося підвищення рівня, якщо сума виявилася рівній 0, такий результат Не несе ніякої певній корисною інформації про рівні (є просто шумом) на даному вході і відкидається, а за рівень соответвуют виходу енкодера приймається попереднє знаення. Тобто для розгляду подій в якості достовірного джерела інформації необхідно що-б були зафіксовані якісь події (лічильник кількості подій Не дорівнює 0) і сума по рівнями цих подій теж Не була дорівнює 0. Цей прийом ефективно вирішує проблему брязкоту контактів і Не вимагає будь-яких додаткових тимчасових затримок. Аналіз стану через кожні 1 мс показав високу ефективність. Тобто опитування енкодера йде реально з максимальної частотою з якої мікроконтролер здатний обробляти переривання, а аналіз інформації, накопиченої при обробці переривань відбувається з частотою порядку 1 кГц, чого більше ніж досить. У час налагодження алгоритму було видно, що за період 1 мс може відбуватися до 40-50 переривань і передбачені мною прийоми обробляють такий значний для мікроконтролера потік інформації досить ефективно. Схему підключення енкодера, думаю, немає сенсу тут показувати, в інтернеті такий інформації вистачає. Виходи енкодера підключені до другого і третього піну, що відповідає нульового і першому перериванням Arduino Uno. енкодер управляє яскравістю світлодіода, підключеного до дев'ятому виходу, щовикористовується в режимі ШІМ. // Encoder pins #define encoder0PinA 2 #define encoder0PinB 3 // LED pin #define LED_PIN 9 // Encoder inputs in high state unsigned short A_high, B_high; // Count of encoder events between two timer events unsigned short A_between = 0, B_between = 0; // Previous decission on encoder state unsigned short A_statePrev = 0, B_statePrev = 0; // Current event id for each encoder input short A_event = 0, B_event = 0; // Id of event that already accounted short A_eventPrev = 0, B_eventPrev = 0; // Timestamps for time measurements unsigned long timeCur, timePrev; // Value for estimation of dominant inputs state short A_level = 0, B_level = 0; int encoder0Pos = 127; int fadeAmount = 10; // Number of events between two event id's int EventsBetween(short iCur, short iPrev) { if ((iCur < 0) && (iPrev < 0)) { return (int(iCur) + 256) - (int(iPrev) + 256); } else if (iCur < 0) { return (int(iCur) + 256) - iPrev; } return iCur - iPrev; } // Keep value in limits int clamp(int value, int low, int high) { if (value < low) { return low; } if (value > high) { return high; } return value; } void setup() { pinMode(encoder0PinA, INPUT); pinMode(encoder0PinB, INPUT); pinMode(LED_PIN, OUTPUT); // encoder pin on interrupt 0 (pin 2) attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 1 (pin 3) attachInterrupt(1, doEncoderB, CHANGE); //Serial.begin(9600); analogWrite(LED_PIN, encoder0Pos); timePrev = millis(); } void loop(){ timeCur = millis(); if (timeCur > timePrev) { timePrev = timeCur; boolean A_change = A_between && A_level; boolean B_change = B_between && B_level; if (A_change || B_change) { boolean A_state, B_state; if (A_change) { A_state = A_level > 0; } else { A_state = A_statePrev; } if (B_change) { B_state = B_level > 0; } else { B_state = B_statePrev; } if ((A_change && ((!A_state && A_statePrev && B_state) || (A_state && !A_statePrev && !B_state))) || (B_change && ((!B_state && B_statePrev && !A_state) || (B_state && !B_statePrev && A_state)))) { encoder0Pos += fadeAmount; } if ((A_change && ((!A_state && A_statePrev && !B_state) || (A_state && !A_statePrev && B_state))) || (B_change && ((!B_state && B_statePrev && A_state) || (B_state && !B_statePrev && !A_state)))){ encoder0Pos -= fadeAmount; } encoder0Pos = clamp(encoder0Pos, 0, 255); analogWrite(LED_PIN, encoder0Pos); A_statePrev = A_state; B_statePrev = B_state; } A_between = 0; B_between = 0; A_level = 0; B_level = 0; } else { if (EventsBetween(A_event, A_eventPrev) >= 1) { A_eventPrev = A_event; if (A_high) { A_level++; } else { A_level--; } A_between++; } if (EventsBetween(B_event, B_eventPrev) >= 1) { B_eventPrev = B_event; if (B_high) { B_level++; } else { B_level--; } B_between++; } } } // Interrupt on A changing state void doEncoderA(){ A_high = (digitalRead(encoder0PinA) == HIGH); A_event++; } // Interrupt on B changing state void doEncoderB(){ B_high = (digitalRead(encoder0PinB) == HIGH); B_event++; } Дата останнього оновлення: 24-2-20 00:11:02 |
||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © 2005-2022 SAGE. Всі права захищено. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||