В очередном проекте было решено использовать STM32F103 (плату Blue Pill) и роторный энкодер в качестве одного из элементов управления. Благодаря заметке Микроконтроллеры STM32: основы использования таймеров, прерываний и ШИМ нам известно, что в мире STM32 эта задача решается при помощи таймеров. Однако само решение продемонстрировано не было. Давайте заполним этот пробел.
Важно! Дешевые роторные энкодеры с AliExpress / eBay попадаются переменного качества. Бывает так, что энкодер работает нормально, но выходит из строя после месяца использования. Чтобы точно не оказаться в подобной ситуации, используйте энкодеры от нормальных производителей. Могу порекомендовать энкодеры серии PEC11 от компании Bourns. Они бывают с кнопкой и без, с щелчками (detents) и без щелчков, а также с разной длиной и формой вала. Подробности описаны в даташите [PDF] .
Для генерации скучного шаблонного кода воспользуемся STM32CubeMX. Во вкладке Pinout & Configuration → Categories → Timers находим и включаем свободный таймер:
В свойствах таймера выбираем Encoder Mode:
Проверяем вкладку Pinout View. Там должно позеленеть два новых пина:
Тут будет не лишним напомнить распиновку типичного роторного энкодера:
Out A должен идти к пину PA8, Out B — к пину PA9. Или наоборот, Out A — к PA9, Out B — к PA8. Это не важно, поскольку вращение энкодера по и против часовой стрелки интерпретирует прошивка.
Далее в свойствах таймера меняем Counter Period:
Это максимальное значение, которое будет принимать счетчик таймера.
Наконец, во вкладке GPIO Settings включаем pull-up резисторы на PA8 и PA9:
Последний шаг необходим, поскольку для правильной работы энкодера Out A и Out B должны быть подтянуты к Vcc.
Нажимаем Generate Code. Теперь мы можем узнать значение счетчика вот так:
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 ;
}
}
На один щелчок энкодера счетчик увеличивается или уменьшается на два, один раз на передний и один раз на задний фронт сигнала. Мне больше нравится считать щелчки, чем фронты сигнала, поэтому счетчик делится на два. При выбранном мной соединении между пинами МК и энкодера счетчик уменьшается при вращении энкодера по часовой стрелке. Мне же хотелось, чтобы было наоборот, а перепаивать было лень. Так в формуле появилось вычитание.
Код в действии:
Здесь использован уже знакомый нам TFT-дисплей на базе ST7735 . Помимо энкодера используется пять кнопок — четыре обычные, и одна встроена в сам энкодер. Кнопки считываются по прерываниям . На дисплее отображается последняя нажатая кнопка, а также счетчик. Счетчик растет при вращении энкодера по часовой стрелке и уменьшается при вращении против часовой. Значение счетчика изменяется на единицу на один щелчок энкодера.
Зная текущее и предыдущее значение счетчика, а также время, прошедшее между получением этих значений, мы всегда можем вычислить, в какую сторону, на какое количество щелчков и с какой скоростью пользователь крутит энкодер. Один из возможных вариантов кода:
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 .