Работа с энкодером. Указатель на функцию

В реальности энкодер используется в более сложных ситуациях, и придётся менять минимум три переменных: яркость дисплея в режиме отображения времени; часы в режиме настройки часов; и минуты в режиме настройки минут. Вычитая из текущего состояния регистра CNT его предыдущее значение мы можем узнать скорость вращения и направление. Но показания счётчика зациклены. Рассмотрим крайние случаи. Пусть счётчик хранит значение MAX_COUNT_VALUE - 1, тогда при повороте по часовой стрелке, он прибавляя 2 перегрузит счётчик и вместо ожидаемых 129 в нём будет записано 1. Другими словами нам нужно, чтобы:

За один оборот вала должно произойти 11 щелчков, согласно документации на энкодер. Человек вряд ли сможет сделать несколько оборотов менее чем, скажем, за 0,25 с, следовательно алгоритм подсчёта разницы можно сделать очень «топорным»: если модуль разницы текущего состояния и предыдущего больше половины от MAX_COUNT_VALUE, то мы, условно (зависит от подключения) крутили вправо, в противном случае влево. Значение счётчика меняется на число кратное двум, это не удобно, поэтому конечный результат нужно поделить на 2.

Подсчитать разницу и как-то обработать её (если она отличная от нуля) можно в главном цикле.

Но это не лучшее решение, ведь время реакции системы непредсказуемо. Можно настроить прерывание по изменению счётчика.

Это лучше с точки зрения отзывчивости системы, но при быстрой прокрутке прерывания будут идти одно за другим подтормаживая всю остальную систему, что уже не так хорошо. Плюсом код обработки счётчика придётся вынести в stm32f10x_it.c, что не очень удобно.

Лучше всего настроить ещё один таймер, который с некоторой периодичностью, например с частотой 4 - 10 Гц (0,25 - 0,1 с), будет вызывать прерывание и как-то реагировать на изменение счётчика. Накладные расходы будут постоянными, но не такими большими, чтобы это как-то сильно сказалось на всю систему. Для удобства заведём структуру.

Добавим в функцию encoder_init() настройку таймера, например TIM4, который мы использовали ранее.

И сам обработчик прерывания.

Чтобы ещё больше развязать логику работы ДУП от действия которое он выполняет (ведь действий может быть несколько, в зависимости от состояния), можно в нашу структуру добавить указатель на функцию и подменять её в зависимости от состояния, например так:

Оставлять указатель action равным 0 плохая идея. Переделаем немного функцию инициализации и принудим пользователя модулем указывать нужную функцию.

Не забудьте поправить прототип функции в заголовочном файле.

Теперь дополним encoder_update() и перепишем главный цикл.

Если вы знакомы с C++, то наверняка заметили — здесь возможностями языка Си реализован «класс». Данная конструкция немного «уродливая», так как Си изначально не объекто-ориентированный язык программирования. В случае, если вы находите привязку функций к некоторому сущности интересной концепцией, то имеет смысл перейти на ООП. Книга «C++ для встраиваемых систем» (в данный момент в состоянии написания) посвящена именно этим вопросам.

Теперь, при повороте энкодера в прерывании вычисляться разность текущего и предыдущего состояния счётчика. Если разность отлична от нуля, то в прерывании будет вызвана функция change_var(). Подменяя указатель, можно добиться разной реакции на поворот энкодера.

Код урока можно найти на github: CMSIS.


Назад | Оглавление | Дальше