Непонятки с энкодером
- Войдите на сайт для отправки комментариев
Пнд, 19/02/2018 - 14:53
Собрал схему. Оптический датчик положения от принтера с родным диском. Подключено к ардуино нано.
Код реализован через прерывания. Все считает. Но при слишком большой скорости вращения просто виснет. Это первое. Второе, при попытке рулить двигателем (просто изменить направление вращения), перестает считать.
Куда копать ?
/* old_encoder - предыдущее значение каналов энкодера new_encoder - новое значение каналов энкодера */ byte old_encoder, new_encoder; /* Энкодер с двумя каналами при вращении будет выдавать коды Грея, например: 00 (0) 10 (2) 11 (3) 01 (1) Ячейки массива ns содержат значения следующего кода для заданного индекса массива. Например если текущее значение каналов энкодера равно 00, то в ячейке массива ns[0] содержится следующее значение равное 2 (в бинарном виде - 10). */ byte ns[4] = {2,0,3,1}; /* counter - текущее значение счетчика энкодера */ long counter=0; boolean Dir=0; /* tmp - предыдущее значение счетчика */ long tmp; void setup() { //start serial connection Serial.begin(115200); pinMode(2, INPUT); pinMode(3, INPUT); pinMode(6,OUTPUT); pinMode(8,OUTPUT); // encoder pin on interrupt 0 (pin 2) attachInterrupt(0, doEncoderAB, CHANGE); // encoder pin on interrupt 1 (pin 3) attachInterrupt(1, doEncoderAB, CHANGE); counter = 0; tmp = 0; // присваиваем текущие значения каналов энкодера old_encoder = PIND>>2; new_encoder = old_encoder; } void doEncoderAB() { noInterrupts(); new_encoder = PIND>>2; if (ns[old_encoder] == new_encoder) { counter++; } else if (ns[new_encoder] == old_encoder) { counter--; } old_encoder = new_encoder; interrupts(); } void loop() { if (tmp != counter) { Serial.print(counter); Serial.print(' '); Serial.println(new_encoder); tmp = counter; } if(counter>=100000){!Dir;}; digitalWrite(6,!Dir);digitalWrite(8,Dir); }Со строкой 73 отказывается работать.
Со строкой 73 отказывается работать.
А что эта строка по-Вашему делает? По-моему, её первая часть "
if(counter>=100000){!Dir;};" не деалет ничего, а Вы как считаете?А по сути, у Вас коды расписаня неправильно. Так неоднозначно получается. Нужно учитывать не только текущее положение, но и предыдущее. Нарисуйте сдвинутые по фазе меандры энкодера и внимательно по ним пройдите.
Со строкой 73 отказывается работать.
А что эта строка по-Вашему делает? По-моему, её первая часть "
if(counter>=100000){!Dir;};" не деалет ничего, а Вы как считаете?Я в курсе, что ничего не делает.
Только вот работать перестает. Изначально в коде писал просто
if(counter>=10000){digitalWrite(6,1);digitalWrite(8,0);}
И точно так же код переставал работать.
А по сути, у Вас коды расписаня неправильно. Так неоднозначно получается. Нужно учитывать не только текущее положение, но и предыдущее. Нарисуйте сдвинутые по фазе меандры энкодера и внимательно по ним пройдите.
Вот же учитываем и старое и новое значение, потом обновляем
void doEncoderAB() 48 { 49 noInterrupts(); 50 new_encoder = PIND>>2; 51 if (ns[old_encoder] == new_encoder) 52 { 53 counter++; 54 } 55 else if (ns[new_encoder] == old_encoder) 56 { 57 counter--; 58 } 59 old_encoder = new_encoder; 60 interrupts(); 61 }Ну, для начала Вы берете из порта 6 битов вместо двух ("new_encoder = PIND>>2;"). Сдвинули Вы на два и взяли оставшиеся шесть битов.
А потом, что Вы учитываете? Я же Вам говорю, нарисуйте картинку и внимательно на неё посмотрите - все варианты рассмотрите.
Кроме того, энкодер в лупе не читают - Вы пропустите половину его изменений. Изменения на пинах нужно считывать через прерывания. Пока не сделаете этого, будет глючить, даже если все состояния правильно распишете. Особенно при быстром вращении.
Ну, или возьмите готовую библиотеку энкодера и не парьтесь.
Да, действительно какая то херь переодически приходит с пинов значение бывает 15.
Подскажите как правильно прописать чтение двух первых битов с порта D ? Или все таки последних ?
Я не знаю какие именно порты Вы собираетесь читать. Но общее правило (допустим читаем порты 1 и 2):
1. считываем весь регистр: uint8_t pb = PORTB;
2. обнуляем ненужные биты: pb &= ~6;
3. сдвигаем вправо, pb >>= 1;
Пример, для битов 1 и 2, отсюда маска 6 и сдвиг на 1.
Да, действительно какая то херь переодически приходит с пинов значение бывает 15.
Подскажите как правильно прописать чтение двух первых битов с порта D ? Или все таки последних ?
new_encoder = PIND & 0x0C; // new_encoder = PIND 2;3;
Мне надо считать порты прерываний PD2 и PD3.
Что бы получилось двухбитное число, которое и будем сравнивать с прошлым таким числом.
new_encoder = PIND & 0x0C; // new_encoder = PIND 2;3;
Ему нужно, чтобы значение было от 0 до 3, так что ещё сдвинуть надо.
Мне надо считать порты прерываний PD2 и PD3.
Боюсь Вы неверно пjнимаете, что Вам на самом деле нужно.
Вам нужно обрабатывать прерывания, а не читать значения их пинов в loop.
Обработать прерывание - значит если значение на пине поменяломь, программа всё бросает и кидается его читать.
Вы же пытаетесь читать в loop - когда очередь подойдёт.
для обработки прерываний используйте attachInterrupt.
У меня в loop вообще только вывод на монитор значений. Все остальное через прерывания и функции
Попробуйте - прерываниена 2 пине. Енкодер подключен на 2й и 3й пин
int val = 0; int val_temp; void setup() { Serial.begin (9600); attachInterrupt(0, encoder, FALLING); } void loop() { if (val != val_temp) { val_temp = val; Serial.println(val); } } void encoder() { byte n = PIND & 0x08; if (n) val++; else val--; }Попробуйте - прерываниена 2 пине. Енкодер подключен на 2й и 3й пин
int val = 0; int val_temp; void setup() { Serial.begin (9600); attachInterrupt(0, encoder, FALLING); } void loop() { if (val != val_temp) { val_temp = val; Serial.println(val); } } void encoder() { byte n = PIND & 0x08; if (n) val++; else val--; }Вообще не вариант. В одном случае пропускает первое изменение положения.
Точнее даже не в одном, а в трёх случаях из четырех.
С битами не работал пока. Пытаюсь постичь эту науку. Толком нигде не разжевано. Читаю дальше.
Пока понял, что мне надо при чтении с порта D
PIND отбросить два старших бита , а остальное сдвинуть на 4 бита вправо.
Упс, выводы начинаются с младших битов. Значит мне надо отбросить 4 старших и сдвинуть вправо на два бита.
О великие гуру, подскажите я не ошибусь если сделаю так ?
new_encoder=(PIND&~4)>>2;
Т.е. мне надо считать значения входов PD2 PD3, я отбрасываю PD4 и выше, и сдвигаю то, что осталось.
Ну я хотя бы уже понял, почему у меня работало, при незадействованных остальных пинах. :-)
Если это и правильно, то понимания не даёт. На всякий случай спасибо.
0x03 это же 3 ? Зачем тогда столько сложностей ?
Да 0х03 это 3. А в чем сложность? Сдвинули, наложили маску. Ну сделайте не сложно.
Да 0х03 это 3. А в чем сложность? Сдвинули, наложили маску. Ну сделайте не сложно.
Сложность в записи, 0x03 заместо 3
Аааа вон оно что :) Да привычка просто. Хотел вообще 0b00000011 написать да подумал что сложно )
Вроде разобрался. Подскажите
new_encoder=(PIND&12)>>2;
Будет иметь точно такой же результат, что и
new_encoder = (PIND>>2)&0x03; ?
Да, тоже самое
Kakmyc если у вас получилось покажите пожалуйста рабочий скетч, тоже ковыряю энкодер от старого принтера, все никак ненайду пример рабочего кода :(
/* old_encoder - предыдущее значение каналов энкодера new_encoder - новое значение каналов энкодера */ byte old_encoder, new_encoder; /* Энкодер с двумя каналами при вращении будет выдавать коды Грея, например: 00 (0) 10 (2) 11 (3) 01 (1) Ячейки массива ns содержат значения следующего кода для заданного индекса массива. Например если текущее значение каналов энкодера равно 00, то в ячейке массива ns[0] содержится следующее значение равное 2 (в бинарном виде - 10). */ byte ns[4] = {2,0,3,1}; /* counter - текущее значение счетчика энкодера */ long counter=0; /* tmp - предыдущее значение счетчика */ long tmp; void setup() { //start serial connection Serial.begin(9600); pinMode(2, INPUT); pinMode(3, INPUT); // один выход энкодера к прерыванию 0 (pin 2) attachInterrupt(0, doEncoderAB, CHANGE); // второй выход энкодера к прерыванию 1 (pin 3) attachInterrupt(1, doEncoderAB, CHANGE); counter = 0; tmp = 0; // присваиваем текущие значения каналов энкодера old_encoder = (PIND&12)>>2;//считываем значения энкодера при старте new_encoder = old_encoder; } void doEncoderAB() { noInterrupts();//отключаем прерывания на время проверки new_encoder = (PIND&12)>>2;//записываем в переменную новое состояние енкодера if (ns[old_encoder] == new_encoder)/*сравниваем с предыдущим и либо инкрементируем положение если состояние пинов энкодера соответствует таблице положений , указанной в массиве*/ { counter++; } else if (ns[new_encoder] == old_encoder)/*либо декрементируем, если не соответствует*/ { counter--; } old_encoder = new_encoder;//обновляем строе значение interrupts();//включаем прерывание } void loop() { if (tmp != counter)//если были изменения выводим положение в монитор вместе с текущим состоянием энкодера { Serial.print(counter); Serial.print(' '); Serial.println(new_encoder); tmp = counter; } }SLOM, а что, с библиотекой Encoder что-то не устраивает?
Ну меня в ней не устроило описание, там такое нагорожено, код нечитабелен.
Это я про встроенную.
А-а-а, ну тогда ладно.
Kakmyc
спасибо, буду вникать.
ЕвгенийП да дело в том что я полохо разбираюсь в ардуино и незнал что имеет значение на какие выводы подключать датчик. оказхывается у ардуино на пинах 2,3 есть какойто прерыватель и там все считается четко. и быстро, чуток кручу энкодер и буквально каждую риску считает, без промаха от 0 до 9500 примерно, за 1 оборот энкодера. (0 1 2 3 4 5 6...9800....)
а вот когда я подключал на контакты 5,6 это был пипец, за один оборот считалось около 100 рисок и шел постоянный промах, числа повторялись (0 1 0 1 2 1 2 3...... )
вобщем начало положен, теперь буду пытатся сделать подобие сервы. чтобы можно было связать движек и круговой энкодер от принтера.
такого плана
фото не мое, пример из интернета, но энкодер похож.
Всё зависит от программы. Прерывания у Ардуины есть на всех пинах, просто те (не на 2-ом и 3-ем) надо по-другому использовать. Если их использовать, то можно вешать на любые пины.
попробовал совместить 2 скетча, один для теста мотора а второй тест энкодера. вот такая кривотень получилась
/* HG7881_Motor_Driver_Example - Arduino sketch This example shows how to drive a motor with using HG7881 (L9110) Dual Channel Motor Driver Module. For simplicity, this example shows how to drive a single motor. Both channels work the same way. This example is meant to illustrate how to operate the motor driver and is not intended to be elegant, efficient or useful. Connections: Arduino digital output D10 to motor driver input B-IA. Arduino digital output D11 to motor driver input B-IB. Motor driver VCC to operating voltage 5V. Motor driver GND to common ground. Motor driver MOTOR B screw terminals to a small motor. */ /* Encoder Library - Basic Example * http://www.pjrc.com/teensy/td_libs_Encoder.html * * This example code is in the public domain. */ #include <Encoder.h> // Change these two numbers to the pins connected to your encoder. // Best Performance: both pins have interrupt capability // Good Performance: only the first pin has interrupt capability // Low Performance: neither pin has interrupt capability Encoder myEnc(2, 3); // avoid using pins with LEDs attached // wired connections #define HG7881_B_IA 5 // D10 --> Motor B Input A --> MOTOR B + #define HG7881_B_IB 6 // D11 --> Motor B Input B --> MOTOR B - // functional connections #define MOTOR_B_PWM HG7881_B_IA // Motor B PWM Speed #define MOTOR_B_DIR HG7881_B_IB // Motor B Direction // the actual values for "fast" and "slow" depend on the motor #define PWM_SLOW 100 // arbitrary slow speed PWM duty cycle #define PWM_FAST 200 // arbitrary fast speed PWM duty cycle #define DIR_DELAY 1000 // brief delay for abrupt motor changes void setup() { Serial.begin( 9600 ); pinMode( MOTOR_B_DIR, OUTPUT ); pinMode( MOTOR_B_PWM, OUTPUT ); digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); Serial.println("Basic Encoder Test:"); } long oldPosition = -999; void loop() { boolean isValidInput; // draw a menu on the serial port Serial.println( "-----------------------------" ); Serial.println( "MENU:" ); Serial.println( "1) Fast forward" ); Serial.println( "2) Forward" ); Serial.println( "3) Soft stop (coast)" ); Serial.println( "4) Reverse" ); Serial.println( "5) Fast reverse" ); Serial.println( "6) Hard stop (brake)" ); Serial.println( "-----------------------------" ); do { byte c; // get the next character from the serial port Serial.print( "?" ); while( !Serial.available() ) ; // LOOP... c = Serial.read(); // execute the menu option based on the character recieved switch( c ) { case '1': // 1) Fast forward Serial.println( "Fast forward..." ); // always stop motors briefly before abrupt changes digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); delay( DIR_DELAY ); // set the motor speed and direction digitalWrite( MOTOR_B_DIR, HIGH ); // direction = forward analogWrite( MOTOR_B_PWM, 255-PWM_FAST ); // PWM speed = fast isValidInput = true; break; case '2': // 2) Forward Serial.println( "Forward..." ); // always stop motors briefly before abrupt changes digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); delay( DIR_DELAY ); // set the motor speed and direction digitalWrite( MOTOR_B_DIR, HIGH ); // direction = forward analogWrite( MOTOR_B_PWM, 255-PWM_SLOW ); // PWM speed = slow isValidInput = true; break; case '3': // 3) Soft stop (preferred) Serial.println( "Soft stop (coast)..." ); digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); isValidInput = true; break; case '4': // 4) Reverse Serial.println( "Fast forward..." ); // always stop motors briefly before abrupt changes digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); delay( DIR_DELAY ); // set the motor speed and direction digitalWrite( MOTOR_B_DIR, LOW ); // direction = reverse analogWrite( MOTOR_B_PWM, PWM_SLOW ); // PWM speed = slow isValidInput = true; break; case '5': // 5) Fast reverse Serial.println( "Fast forward..." ); // always stop motors briefly before abrupt changes digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); delay( DIR_DELAY ); // set the motor speed and direction digitalWrite( MOTOR_B_DIR, LOW ); // direction = reverse analogWrite( MOTOR_B_PWM, PWM_FAST ); // PWM speed = fast isValidInput = true; break; case '6': // 6) Hard stop (use with caution) Serial.println( "Hard stop (brake)..." ); digitalWrite( MOTOR_B_DIR, HIGH ); digitalWrite( MOTOR_B_PWM, HIGH ); isValidInput = true; break; default: // wrong character! display the menu again! isValidInput = false; break; } long newPosition = myEnc.read(); if (newPosition != oldPosition) { oldPosition = newPosition; Serial.println(newPosition); if (newPosition = 30000){ digitalWrite( MOTOR_B_DIR, LOW ); digitalWrite( MOTOR_B_PWM, LOW ); } } } while( isValidInput == true ); // repeat the main loop and redraw the menu... }задумка была такая что включив тест мотор крутит до тех пор пока энкодер ненасчитает 30 000, но минуло 80 000 а он и не думал останавливатся.
а вообще пытаюсь добится результата, при котором можно написать чтото типа таблицы со значениями по которым долен работать покрутил до значения 20 000 потом остановился на время задержки, потом покрутил до -10 000 и снова остановился, потом до 40 000 и тд....
Нет, это так не делается.
1.
Сравнение в строке 166 может не сработать никогда. newPosition на одном шаге может быть 29999, а на следующеи уже успело тикнуть два раза и стало 30001 - всё, Вы пропустили и движок крутится дальше.
Вообще, это сравнение лучше оставить в обработчике прерывания, тогда оно будет проверять точно когда сменилось значение, а не "когда придётся", как это сделано сейчас.
2.
При каждом запуске надо сбрасывать энкодер в ноль. Иначе он будет считать стартуя с позиции, оставшейся от предыдущего запуска.
ЕвгенийП а у вас нет таких примеров?
сделал так
а что печатает в строке 163?
И вообще, давайте сюда протокол.
в строке 163 печатает только при нажатии ентер, хотелось бы конечно чтобы оно выводило информацию непрерывно, но увы.
происходит это так:
1. все подключаю и запускаю монитор порта. он выводит
далее жму 1 и энтер. получаю:
мотор крутит и ничего в мониторе не происходит, жму 3 + энтер и получаю:
вот както так этот код и работает.
а у меня есть сканер от принтера. тоже кэнон и судя по фото внутрянка сканера идентична.
вот потипу такого
хочу вместо фото датчика поставить светодеодную адресную ленту, будет переливаться цветами. а каретку заставить плавно двигаться от края до края. получиться фейковый сканер. думаю детям понравится, руку прикладывать и сканировать :)
но пока немогу понять как на основе примена библиотеки енкодера, сделать так чтобы мотор начинал движение из точки 0 до точки N и обратно....
нашел интересную ссылку
http://homofaciens.de/technics-base-circuits-encoder-disc_en.htm
там есть описание как подключать энкодеры и видео, в нем на 25 минуте есть такой же энкодер как я использую в опытах. (у меня их много было, 2 штуки сжег на стадии "как это подключить" еще 2 осталось)
так вот там есть ссылка на архив в котором есть скетч printer_motor :
поидее, как я понял из этого
if(readByte > 0){ if(readByte == 20){ setPoint++; } if(readByte == 19){ setPoint--; } if(readByte == 18){ setPoint+=2875; } if(readByte == 17){ setPoint-=2875; } if(readByte == 16){ setPoint+=100; } if(readByte == 15){ setPoint-=100; } if(readByte == 14){ setPoint+=10; } if(readByte == 13){ setPoint-=10;если в монитор порта отправлять значения 13, 14, 15 и тд.... то мотор будет крутить до соответствующих им данных энкодера.
но оно работает както не так.
при включении мотор делает гдето пол оборота и стоит, при отправки любого сообщения он начинает просто крутить и все... на послдующие сообщения не реагирует.
Ну, так понятно. у Вас же блокировка стоит в строке 85.
Т.е. Вы запустили двигатель и, вместо того чтобы следить за ним, сели на мёртвое ожидание нажатия клавиши в 85 строке и сидите в нём. В двигатель и крутится себе.
Удаляйте блокировку и делайте аккуратную проверку - пришло что-нибудь - читаем, нет - идём контролировать двигатель.
в каком именно коде? что именно удалить и что вместо него поставить?
Вы что, этот код не сами писали?
увы сам неумею, все что смог найти в инете, пытаюсь по возможности переделать.
к сожалению тема энкодеров нераскрыта, уже 3 дня рою как крот, так и не нашел ни одного вменяемого примера где бы были связаны мотор и энкодер.
неужели никто их неиспользует.
Причём тут вообще энкодер? Вы стали в мёртвом цикле в строке 85 и ничего не делаете, наплевав на то, что мотор у Вас крутитс и за ним надо следить. Вам нужно убрать этот мёртвый цикл, а поставить аккуратную, неблокирующую проверку пришло что-то или нет. Делайте, пытайтесь, учитесь.
были бы примеры былоб легче.
а их реально нет, вот и делаю вывод что энкодеры в паре с движками никто не использует.
Я планировал заняться парой энкодер/двигатель. Но к сожалению спалил датчик(как оказалось датчики все разные и перед подключением их надо изучать.).
Сейчас пока времени нет, появится куплю очередной принтер и займусь.
Ещё раз говорю, то о чём я пишу в последних постах не имеет никаого отношения ни к эгкодеру, ни к двигателю - это вопрос построения программы. Берите любую книгу по С - читайте что оназначает Ваша 85-ая строка (Вы похоже, этого не знаете), а когда поймёте, продолжайте читать и думайте на что её можно заменить. Про двигатели и энкодеры пока забудьте.
Kakmyc я тоже сжег 2 штуки, подключал 5в потом подключил 3,3в и норм. :)
Евгений
поймите в данном случае мне не нужна удочка, мне нужна рыбка. да познать программирование это хорошо, но реально мне не так часто удается найти время на свое хобби. у меня 2е детей один из них еще сеосем мелкий, вобщез забот хватает... + работа и тд... поэтому тратить время на углубленное изучение программирования нехочу. ищу пример где бы было реализовано что то похожее, слегка подправить под свои нужды и все.
на то она и ардуина, на то и есть такие форумы где люди делятся готовыми скетчами. чтобы простым обывателям, вроде меня, было проще сделать ту или иную поделку.