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

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

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

Что такое этот энкодер, думаю, смысла говорить нет. Это легко гуглится. Выглядит эта штука вот так: инкрементальный
У него есть 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.
  • 0

Комментарии (1)

0
У меня такой механический энкодер лагает иногда при повороте (вероятно из-за дребезга контактов)
У вас есть такая проблема и как вы с ней боритесь?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.