Ардуинка + инкрементальный энкодер
- Войдите на сайт для отправки комментариев
Вс, 31/03/2013 - 22:44
Добрый вечер уважаемые форумчане!
Пытаюсь подключить икремент оптический энкодер к ардуине уно. Помогите пожалуйста найти ошибку. Я написал простенькую програмку на внешних прерываниях по каналу А и по метке оборотов Z. Когда кручу энкодер медленно, считает импульсы правильно. Начинаю крутить (относительно) быстро начинается расколбас счетчиков.
Вот скетч:
#define encoder0PinA 2 //вход А пин 2 #define encoder0PinB 7 //вход B пин 7 #define encoder0PinZ 3 //вход Z пин 3 volatile long encoder0Pos = 0.0; // переменная в обработчике прерываний по подсчету импульсов volatile long encoder0Rotations = 0.0; // переменная по подсчету оборотов. void setup() { pinMode(encoder0PinA, INPUT); pinMode(encoder0PinB, INPUT); pinMode(encoder0PinZ, INPUT); // encoder pin on interrupt 0 (pin 2) attachInterrupt(0, doEncoderA, RISING); //функция обработки внешнего прерывания INT0 // encoder pin on interrupt 1 (pin 3) attachInterrupt(1, doEncoderZ, RISING); //функция обработки внешнего прерывания INT1 Serial.begin (9600); Serial.println("START READING"); } void doEncoderA(){ // функция действие вызываемое прерыванием1 // look for a low-to-high on channel A if (digitalRead(encoder0PinA) == HIGH) { // check channel B to see which way encoder is turning if (digitalRead(encoder0PinB) == LOW) { encoder0Pos = encoder0Pos + 1; // по Ч.С. } else { encoder0Pos = encoder0Pos - 1; // против Ч.С. } } else // must be a high-to-low edge on channel A { // check channel B to see which way encoder is turning if (digitalRead(encoder0PinB) == HIGH) { encoder0Pos = encoder0Pos + 1; // по Ч.С. } else { encoder0Pos = encoder0Pos - 1; // против Ч.С } } } void doEncoderZ() { if(digitalRead(encoder0PinZ) == HIGH) { encoder0Pos = 0; encoder0Rotations = encoder0Rotations +1; // delay(50); } } void loop () { Serial.print(" "); Serial.print(encoder0Pos); Serial.print(" "); Serial.print(encoder0Rotations); Serial.print(" \n "); delay(10); }
Работают счетчики правильно если крутить в одну сторону, но я так и кручу.
При плавном кручении в одну сторону счетчик оборотов encoder0Pos идет по возрастающей и при получении метки Z сбрасывается и прибавляет отмеренный оборот. При ускорении счетчик encoder0Pos резко начинает прибавлять -1 вместо +1. и может насчитать даже больше импульсов чем фактическое число импульсов на оборот (3000имп).
энкодер http://www.megachip.ru/pdf/AUTONICS/E30S4.pdf подключаю его напрямую к ардуине. подсоединяю питание- земля-A-B-Z
Ps. извиняюсь, вставил предыдущий файл
эти строчки лишние. отредактировать не найду в каком меню.(для параметра Change в вызове Аттач интерраптс())
30 else // must be a high-to-low edge on channel A
31 {
32 // check channel B to see which way encoder is turning
33 if (digitalRead(encoder0PinB) == HIGH) {
34 encoder0Pos = encoder0Pos + 1; // по Ч.С.
35 }
36 else {
37 encoder0Pos = encoder0Pos - 1; // против Ч.С
38 }
39 }
Привет коллега..:)
Я тоже таким проектом занимаюсь.. Подскажу вот что..По внешним прерываниям (INT) ты(можно на ты?) не сможешь посчитать кол-во импульсов..Тебе надо их заводить на тактирующие входа таймеров. Внешнее прерывание можно использовать на 0-метку, для начала отсчета, а в прерывании уже настроить таймер/счетчик на подсчет входящих импульсов..Я так и сделал..Считает идеально, но один минус -- можно одновременно задействовать только один канал, т.к. на плате Ардуины( у меня Мега R3) разведен только 1 16-разр. счетчик, ост. у меня не задействованы..Но для меня это не проблема -- я потом ост.разведу..
Вот здесь arduino.ru/forum/programmirovanie/preryvaniya-po-taimeru есть приблизительный код моей программы..Если надо, то могу выложить нормальный рабочий скеч, подправишь под свою платку Ардуины и бут все ОК..
Привет - привет! На ты можно.
а почему нельзя по внешним прерываниям? люди же пишут что работает - http://easyelectronics.ru/avr-uchebnyj-kurs-inkrementalnyj-enkoder.html
от туда скопипастил - "Пунктиром показано предполагаемое положение в произвольный момент. Красные стрелки это фронты по которым сработают прерывания при движении либо в одну, либо в другую сторону.
А в обработчике прерывания INT0 щупаем вторым выводом канал В. И дальше все элементарно! Если там высокий уровень — делаем +1, если низкий -1 нашему счетному регистру. Кода на три строчки, мне даже писать его лень."
Для каких тогда целей нужны внешние прерывания если по ним нельзя считать считать состояние импульса?
А по прерываниям по таймеру - идея такова, что загоняем с каждым тиком таймера в прерывание и смотрим есть ли сигнал или нету?
И если можешь, поделись скетчем)
Здесь библиотека для энкодеров лежит, достаточно быстрая(до 127 кгц).
http://www.pjrc.com/teensy/td_libs_Encoder.html
Запустил, но почему-то считает импульсов за оборот в 4 раза больше чем на диске энкодера
Запустил, но почему-то считает импульсов за оборот в 4 раза больше чем на диске энкодера
Есть такое, я думал, что у меня энкодеры такие - делю результат на четыре перед сбрасыванием значения в переменные и умножаю при возвращении из переменных.
зы
Вам шашечки или ехать? :D
. Красные стрелки это фронты по которым сработают прерывания при движении либо в одну, либо в другую сторону.
Это в теории, а в практике - лучше смотреть на падение. Я на это недели убил. Пока анализатором не посмотрел что же там происходит. По крайней мере на шумящих энкодерах. По крайней мере на моем так вышло.
И если можешь, поделись скетчем)
Дык делились уже. Чуть чуть покопатся в поиске и можно найти ветку где уже обсасывали энкодер вдоль и по диагонали
Используем Энкодер | Аппаратная платформа Arduino
и еще ветки есть.
Как то так...
У меня МЕГА R3 и все сделано под нее..Пины входов могут не совпадать с другими..
Начало отсчета(0-метку) повесили на внешнее прерывание(INT0)..По приходу события запускаем обработчик, в котором включаем таймер на внешнее тактирование(Т5) и по проходу одного оборота датчика отключаем внешнее тактирование..
чот как то сложновата у вас программа.
как то пришлось делать контроллер для пневматического шд, там стоял датчик инкрементный...
подключил его канал А на int0(в прогр. PIND2_bit), В на int1(в прогр. PIND3_bit) соответственно. Внешние прерывания настроил на прерывание по фронту.
void INT0_ISR() org IVT_ADDR_INT0{ // на сложение
if(PIND3_bit)crd--; else crd++;
}
void INT1_ISR() org IVT_ADDR_INT1{ // на вычитание
if(PIND2_bit)crd++; else crd--;
}
вроде все щитает, одно НО... когда датчик медленно крутился все отлично работало, но когда все стало на реальный привод, наступила веселуха, начались проскакивания координат и т.п(датчик то был на 90000 меток на оборот :) ). Оказалось, что меге тупо не хватало производительности для обработки прерываний...Пришлось делать етот счетчик аппаратным :)
Щаc взял себе Arduin Due, буду пробовать вешать датчик на его таймер, там есть модуль для работы с инкрементными датчиками.
Может быть,
digitalRead()
слишком долго работает. Попробуйте заменить на PORTD & (1 << 2), PORTD & (1 << 3), PORTD & (1 << 7)чот как то сложновата у вас программа.
как то пришлось делать контроллер для пневматического шд, там стоял датчик инкрементный...
подключил его канал А на int0(в прогр. PIND2_bit), В на int1(в прогр. PIND3_bit) соответственно. Внешние прерывания настроил на прерывание по фронту.
void INT0_ISR() org IVT_ADDR_INT0{ // на сложение
if(PIND3_bit)crd--; else crd++;
}
void INT1_ISR() org IVT_ADDR_INT1{ // на вычитание
if(PIND2_bit)crd++; else crd--;
}
вроде все щитает, одно НО... когда датчик медленно крутился все отлично работало, но когда все стало на реальный привод, наступила веселуха, начались проскакивания координат и т.п(датчик то был на 90000 меток на оборот :) ). Оказалось, что меге тупо не хватало производительности для обработки прерываний...Пришлось делать етот счетчик аппаратным :)
Щаc взял себе Arduin Due, буду пробовать вешать датчик на его таймер, там есть модуль для работы с инкрементными датчиками.
Ну у меня тоже примерно так же с энкодером, но у вас 90.000 на оборот, а у меня 3000, мб и этого тоже много для ардуинки(у меня уно).
А как определили что нехватало именно производительности ардуины??
И расскажите потом как там с Due дела будут)
ЗЫ. А кстати если датчик на таймер вешать, то мне кажется вероятность пропуска импульса будет гораздо выше чем при ловле их по прерываниям. Если привод (в среднем, не очень быстро) крутить со скоростью 100 об/мин= 1.6об/сек = 144000 имп/сек, то случаем процессорное время не урежется ли для других операций?
Может быть,
digitalRead()
слишком долго работает. Попробуйте заменить на PORTD & (1 << 2), PORTD & (1 << 3), PORTD & (1 << 7)Поясните пожалуйста по подробнее, я в битовых операциях при обращении к определенным регистрам нуб)
а я производительность очень просто проверял: в основном цикле программы оставлял мигалку для светодиода, ну и увеличивал скорость вращения датчика... осцилографом смотрел на выход... там правда и на глаз все видно сразу было :)...у меня в основной программе еще была обработка управляющих команд с сом-порта и кое какая математика для управлением приводом, да и задача стояла исследовать динамику привода...
вот например что выдает ассемблер от microC(ниже листинг):
;isr_mstr.c,121 :: void INT0_ISR() org IVT_ADDR_INT0{ // на сложение
;isr_mstr.c,122 :: if(PIND3_bit)crd--; else crd++;
0x07BA 0xB3B0 IN R27, PIND3_bit+0
0x07BC 0xFFB3 SBRS R27, 3
0x07BE 0xC005 RJMP L_INT0_ISR44
0x07C0 0xE0B1 LDI R27, 1
0x07C2 0x1A6B SUB crd+0, R27
0x07C4 0xE0B0 LDI R27, 0
0x07C6 0x0A7B SBC crd+1, R27
0x07C8 0xC004 RJMP L_INT0_ISR45
L_INT0_ISR44:
0x07CA 0xEFBF LDI R27, 255
0x07CC 0x1A6B SUB crd+0, R27
0x07CE 0xEFBF LDI R27, 255
0x07D0 0x0A7B SBC crd+1, R27
L_INT0_ISR45:
;isr_mstr.c,123 :: }
L_end_INT0_ISR:
0x07D2 0x91BF POP R27
0x07D4 0xBFBF OUT SREG, R27
0x07D6 0x91BF POP R27
0x07D8 0x91FF POP R31
0x07DA 0x91EF POP R30
0x07DC 0x9518 RETI
; end of _INT0_ISR
под координату у меня уходило 4 байта, т.е. надо было в обработчике складывать два 4-байтных числа, если усреднить то ето гдето 20 команд=20 циклов(приблизительно) ~ 1.25 мкс( при кварце на 16МГц) на прерывание...т.к. смотрим 2 входа и у меня 90000 имульсов и скорость поворта взять 1об/с, то раз в 1/180000 = 5.6 мкс будут приходить запросы на прерывание, т.е. видите сильно не разгонишся... все время процессора будут съедать ети 2 прерывания.
Вот и пришлось счетчик для датчика делать на тригерах, цеплять к ним мелкую мегу и по SPI скармливать на основной контроллер координату :)
для Онотоле:
вот глянь неплохой сайт, много примеров на С разбирают, да и много чего полезного есть
http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-chast-4.html (здесь про битовые операции расписано)
Я вот не понимаю, зачем Вам эти прерывания сдались..И digitalRead..??
Они пропускают импульсы, особенно на больших оборотах..Тот код, что я привел(внешнее тактирование) работает на датчике 2500 имп/об на скорости ~ 1500-5000 об/мин(больше не проверял) -- считает все исправно..Да мне и не надо такие скоростя, я обхожусь 100-200 об/мин для точной настройки датчика, т.к. которые приходят с завода(т.н. новые) имеют скважность 40-60%. Рекламациями ничего не добились, вот и пришлось сварганить стендик, так сча скважность настраиваем 50%+-1(даже осцилл. сча не надо -- все делается без него)..Да и работать они(и станки) стали заметно стабильнее..
да вот в образовательных целях поставлено задание такое- сделать на прерываниях и чтоб стабильно работало))
Я вот не понимаю, зачем Вам эти прерывания сдались..И digitalRead..??
Они пропускают импульсы, особенно на больших оборотах..Тот код, что я привел(внешнее тактирование) работает на датчике 2500 имп/об на скорости ~ 1500-5000 об/мин(больше не проверял) -- считает все исправно..Да мне и не надо такие скоростя, я обхожусь 100-200 об/мин для точной настройки датчика, т.к. которые приходят с завода(т.н. новые) имеют скважность 40-60%. Рекламациями ничего не добились, вот и пришлось сварганить стендик, так сча скважность настраиваем 50%+-1(даже осцилл. сча не надо -- все делается без него)..Да и работать они(и станки) стали заметно стабильнее..
неплохой вариант, только вот вопрос... Из программы понятно как вы количество импульсов от датичка ловите, а вот как с определением направления вращения?(Не стал лезть в даташиты). На сколько помню там вроде один вход для внешней синхронизации
неплохой вариант, только вот вопрос... Из программы понятно как вы количество импульсов от датичка ловите, а вот как с определением направления вращения?
Почерпнул инфу вот отсюда..http://arduino.ru/forum/apparatnye-voprosy/ispolzuem-enkoder
Немножко переделал код под свои нужды и все показывает исправно..
Повторюсь, это стенд для проверки и настройки, и мне не важна мах скорость..Мне важна правильная настройка датчика..
если вы проверяетет датчик, то вам хватает для етого 1/4 его точности?
кстати кому интересно вот ссылочка про енкодеры
если вы проверяетет датчик, то вам хватает для етого 1/4 его точности?
А при чем здесь 1/4...? Я не вел речь об одном канале..Да и датчики у меня без Грея -- для таких у нас другой стенд..:)
Регистр PORTD содержит состояния пинов с нулевого по седьмой (PORTD & (1 << BIT) ) равно true, если на пине под номером BIT высокий уровень и faulse если наоборот.
digitalRead()
наработает долго и находится в обработчике прерывания, если во время прерывания возникнет другое прерывание(это может произойти, если интервал между фронтами мал, т. е. при большой скорости вращения), то оно может быть пропущено. Отсюда ошибки в определении направления и скорости вращения.Регистр PORTD содержит состояния пинов с нулевого по седьмой (PORTD & (1 << BIT) ) равно true, если на пине под номером BIT высокий уровень и faulse если наоборот....
К сожалению ваш пример не заработает, так как ригистр PORTх предназначен для управления портом, для считывания состояния порта нужно использовать регистр PINх.
http://easyelectronics.ru/avr-uchebnyj-kurs-ustrojstvo-i-rabota-portov-vvoda-vyvoda.html
Вы правы.
Тоже купил энкодер для работы с NetIO . Может кому пригодится код для переделки.
Управление RGB диодом.
Доброго времени суток !
Онотоле, попробуйте в вашей первоначальной программе
второй attach вообще удалите
а оброботчик сделайте так:
И чуть не забыл - тип long целочисленный и устанавливается 0 а не 0.0
Как то так...
У меня МЕГА R3 и все сделано под нее..Пины входов могут не совпадать с другими..
Начало отсчета(0-метку) повесили на внешнее прерывание(INT0)..По приходу события запускаем обработчик, в котором включаем таймер на внешнее тактирование(Т5) и по проходу одного оборота датчика отключаем внешнее тактирование..
объясните ,пожалуйста, начинающему)) - как проиходит отображение результата в коде(lcd.print(z_imp))?
если я правильно "нарыл" - то результат счетчика - в регистрах TCNT5L,TCNT5H
???