Удаленное управление. События. Энкодер инкрементальный.

  • Автор
И снова здравствуйте!

Отрыл сегодня инкрементальный енкодер на полке. Попытаюсь описать как с ним работать и как снимать направление вращения.

Что такое этот энкодер, думаю, смысла говорить нет. Это легко гуглится. Выглядит эта штука вот так: инкрементальный
У него есть 3 пина и если посмотреть на железку сверху, то по часовой стрелке можно эти пины обозначить как A, O, B.
Пин O - пин, работающий на выход, пины A и B работают на вход, то есть "читают" состояние пина O.
Теперь ситуация:
Поворачиваем енкодер по часовой стрелке и ...
Сначала меняется состояние пина A
потом B
потом меняется состояние пина A обратно
затем снова B возвращает свое первоначальное значение. Картина, в общем такая:
(Картинка не моя; взял с интернета)
(Картинка не моя; взял с интернета)

Можно заметить, что в один момент времени пины принимают одинаковое значение, НО при этом имеют разный расклад на предыдущем шаге. Таким образом мы можем понять в какую сторону крутится ручка енкодера.
Покажу на примере ferro-remote как можно это обработать и сделать простейший счетчик, который при повороте по часовой стрелке увеличивает значение, а против - уменьшает.
Тут я описывал как можно ловить события (сигналы, прерывания ... называть можно как угодно) с пинов GPIO, работающих в режиме IN. Думаю повторить тоже самое для данного примера.
Возьмем 3 пина на RPi: GPIO 2, 3, 4.
2 будет A
3 будет O
4 будет B
Для начала простой скрипт, который их откроет и расставит им верное направление
-- gpio-encoder.lua
gpio = fr.client.gpio -- алиас

current = 0 -- сам счетчик. Просто глобальная переменная

function main( argv )

fr.run( ) -- нужно ждать события, скажем не завершать исполнение после main

-- откроем 3 пина 2, 4 - IN 3 - OUT
local A = assert(gpio.export( 2, "in" ))
local O = assert(gpio.export( 3, "out" ))
local B = assert(gpio.export( 4, "in" ))

-- для пинов A и B установим edge в значение both
assert(A:set( "edge", "both" ))
assert(B:set( "edge", "both" ))

end

Если такой скрипт исполднить lua_client'ом, то делать он не будет ничего, кроме того, что получит объекты для работы с пинами, а потом прибьет их сборщиком мусора.

Добавлю-ка я ему обработку события. Тут сделаю так (я видел в интернетах, что у народа довольно большие проблемы с энкодерами и логикой работы с ними. Хотя ничего страшного и сложного нет):
Оба события будет обрабатывать один и тот же обработчик. Можно сделать ДВА разных обработчика, но получится длинее и не уверен, что проще. В параметры обработчику будет передаваться признак того, какой именно пин сработал. false - сработал пин A, true - пин B. Мысль дальше: Чтобы понять как вращается ручка, в момент установки пина на 1, нужно уметь получить состояние другого пина, если оно установлено в 1, значит мы должны увеличить или уменьшить счетчик.
Если мы в обработчике пина B и пин A изменен, то мы вращаем по часовой стрелке и нужно прибавить и наоборот. добавим это в код:

-- тут:
-- pin - false(А) или true(B)
-- states - таблица, которая изначально имеет такой вид: {[false] = 0, [true] = 0}
-- то есть оба состояние выставлены на 0
function change_handler( data, pin, states )

-- directions это просто вспомогательная таблица
-- суть ее в том, что в случае вращения по часовй стрелке
-- к счетчику будет прибавляться 1, а против -- -1
local directions = { [false] = -1, [true] = 1 }
states[pin] = data.value -- установит состояние текущего пина в текущее значение

-- states[not pin] - состояние соседнего пина
-- если текущий пин установлен и соседний тоже установлен
if data.value == 1 and states[not pin] == 1 then
current = current + directions[pin] -- прибавим или отнимем
print( current )
end
end


и того полный скрипт выглядит так:
-- gpio-encoder.lua
gpio = fr.client.gpio

current = 0

function change_handler( data, pin, states )

local directions = { [false] = -1, [true] = 1 }
states[pin] = data.value

if data.value == 1 and states[not pin] == 1 then
current = current + directions[direct]
print( current )
end
end

function main( argv )

fr.run( )

local states = {[true] = 0, [false] = 0}
local A = assert(gpio.export( 2, "in" ))
local O = assert(gpio.export( 3, "out" ))
local B = assert(gpio.export( 4, "in" ))

assert(A:set( "edge", "both" ))
assert(B:set( "edge", "both" ))

-- подпишемся на события от GPIO
assert(A:subscribe( "on_changed", change_handler, false, states, A )) -- пин А false
assert(B:subscribe( "on_changed", change_handler, true, states, B )) -- пин B true

end


Теперь код можно сохранить (например как gpio-encoder.lua) и выполнить lua_client'ом. 192.168.1.11:12345 - адрес и порт малины. Если при открытии не вылетело никаких ошибок, то можно покрутить ручку после запуска.
$ lua_client -s 192.168.1.11:12345 -e gpio-encoder.lua
1
2
3
4
5
4
3
2
1
0
-1
-2
-3
-4
-3
-2
-1
0
1
2
1
0
-1

Работает! Можно использовать.
PS: а нет никакого PS.

Комментарии:

raspiman
У меня такой механический энкодер лагает иногда при повороте (вероятно из-за дребезга контактов)

У вас есть такая проблема и как вы с ней боритесь?

Для написания комментариев необходимо зарегистрироваться