← На главную

Микроконтроллеры STM32: работа с роторным энкодером

В очередном проекте было решено использовать STM32F103 (плату Blue Pill) и роторный энкодер в качестве одного из элементов управления. Благодаря заметке Микроконтроллеры STM32: основы использования таймеров, прерываний и ШИМ нам известно, что в мире STM32 эта задача решается при помощи таймеров. Однако само решение продемонстрировано не было. Давайте заполним этот пробел.

Важно! Дешевые роторные энкодеры с AliExpress / eBay попадаются переменного качества. Бывает так, что энкодер работает нормально, но выходит из строя после месяца использования. Чтобы точно не оказаться в подобной ситуации, используйте энкодеры от нормальных производителей. Могу порекомендовать энкодеры серии PEC11 от компании Bourns. Они бывают с кнопкой и без, с щелчками (detents) и без щелчков, а также с разной длиной и формой вала. Подробности описаны в даташите [PDF].

Для генерации скучного шаблонного кода воспользуемся STM32CubeMX. Во вкладке Pinout & Configuration → Categories → Timers находим и включаем свободный таймер:

Шаг 1: включаем таймер TIM1

В свойствах таймера выбираем Encoder Mode:

Шаг 2: выбираем Encoder Mode

Проверяем вкладку Pinout View. Там должно позеленеть два новых пина:

Шаг 3: проверяем Pinout View

Тут будет не лишним напомнить распиновку типичного роторного энкодера:

Распиновка роторного энкодера

Out A должен идти к пину PA8, Out B – к пину PA9. Или наоборот, Out A – к PA9, Out B – к PA8. Это не важно, поскольку вращение энкодера по и против часовой стрелки интерпретирует прошивка.

Далее в свойствах таймера меняем Counter Period:

Шаг 4: меняем Counter Period

Это максимальное значение, которое будет принимать счетчик таймера.

Наконец, во вкладке GPIO Settings включаем pull-up резисторы на PA8 и PA9:

Шаг 5: включаем pull-up резисторы

Последний шаг необходим, поскольку для правильной работы энкодера Out A и Out B должны быть подтянуты к Vcc.

Нажимаем Generate Code. Теперь мы можем узнать значение счетчика вот так:

int32_t prevCounter = 0; void init() { // ... пропущено ... // так можно проставить начальное значение счетчика: // __HAL_TIM_SET_COUNTER(&htim1, 32760); // не забываем включить таймер! HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL); } void loop() { int currCounter = __HAL_TIM_GET_COUNTER(&htim1); currCounter = 32767 - ((currCounter-1) & 0xFFFF) / 2; if(currCounter != prevCounter) { char buff[16]; snprintf(buff, sizeof(buff), "%06d", currCounter); // выводим куда-то currCounter // ... пропущено ... prevCounter = currCounter; } }

На один щелчок энкодера счетчик увеличивается или уменьшается на два, один раз на передний и один раз на задний фронт сигнала. Мне больше нравится считать щелчки, чем фронты сигнала, поэтому счетчик делится на два. При выбранном мной соединении между пинами МК и энкодера счетчик уменьшается при вращении энкодера по часовой стрелке. Мне же хотелось, чтобы было наоборот, а перепаивать было лень. Так в формуле появилось вычитание.

Код в действии:

Пример работы с роторным энкодером на STM32

Здесь использован уже знакомый нам TFT-дисплей на базе ST7735. Помимо энкодера используется пять кнопок – четыре обычные, и одна встроена в сам энкодер. Кнопки считываются по прерываниям. На дисплее отображается последняя нажатая кнопка, а также счетчик. Счетчик растет при вращении энкодера по часовой стрелке и уменьшается при вращении против часовой. Значение счетчика изменяется на единицу на один щелчок энкодера.

Зная текущее и предыдущее значение счетчика, а также время, прошедшее между получением этих значений, мы всегда можем вычислить, в какую сторону, на какое количество щелчков и с какой скоростью пользователь крутит энкодер. Один из возможных вариантов кода:

void loop() { int32_t currCounter = __HAL_TIM_GET_COUNTER(&htim1); currCounter = 32767 - ((currCounter-1) & 0xFFFF) / 2; if(currCounter > 32768/2) { // Преобразуем значения счетчика из: // ... 32766, 32767, 0, 1, 2 ... // в значения: // ... -2, -1, 0, 1, 2 ... currCounter = currCounter - 32768; } if(currCounter != prevCounter) { int32_t delta = currCounter-prevCounter; prevCounter = currCounter; // защита от дребезга контактов и переполнения счетчика // (переполнение будет случаться очень редко) if((delta > -10) && (delta < 10)) { // здесь обрабатываем поворот энкодера на delta щелчков // delta положительная или отрицательная в зависимости // от направления вращения // ... } } HAL_Delay(100); }

Полную версию исходников к посту вы найдете в этом репозитории на GitHub.