‮Сдвиг по фазе (kincajou) wrote,
‮Сдвиг по фазе
kincajou

Category:

worklog: инкрементальный энкодер и STM32


Некоторые (но не все, сверяйтесь с даташитами) таймеры в STM32 имеют режим работы в качестве счётчика для инкрементального квадратурного энкодера. Для этого используется ввод внешних сигналов TI1 и TI2. Инициализация таймера выглядит примерно так (GPIO уже настроен как надо, таймер TIM4, чип STM32F746):

	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
	RCC->APB1RSTR |= RCC_APB1RSTR_TIM4RST;
	RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM4RST;

	TIM4->CR1 = 0;
	TIM4->CR2 = 0;
	TIM4->PSC = 0; // прескалера нет
	TIM4->CNT = 0;
	TIM4->ARR = -1; // roll-over через тут
	TIM4->SMCR = (0x03 << TIM_SMCR_SMS_Pos); // Encoder mode 3
	TIM4->DIER = 0; // ни DMA, ни прерывания не используются;
	TIM4->DCR = 0;
	TIM4->CCMR1 =
			(0x0C << TIM_CCMR1_IC1F_Pos) | // fSAMPLING=fDTS/16, N=8
			(0x0C << TIM_CCMR1_IC2F_Pos) | // fSAMPLING=fDTS/16, N=8
			(0x01 << TIM_CCMR1_CC2S_Pos) | // CC2 channel is configured as input, IC2 is mapped on TI2
			(0x01 << TIM_CCMR1_CC1S_Pos); // CC1 channel is configured as input, IC1 is mapped on TI1
	TIM4->CCMR2 = 0;
	TIM4->CCER =
			(0 << TIM_CCER_CC1P_Pos) | // неинвертирующий вход
			(0 << TIM_CCER_CC2P_Pos); // неинвертирующий вход
	TIM4->CR1 |= TIM_CR1_CEN; // включить таймер


В этом режиме таймер представляет собой просто делитель-счётчик, одно- или двунаправленный (в зависимости от настроек в SMCR). При таких настройках таймера на один логический отсчёт энкодера значение счётчика меняется на плюс-минус 4 (т.к. это два фронта/спада двух сигналов), в более высокоуровневом коде это надо учитывать. Например, в большинстве распространённых механических энкодеров "с щелчком" (Alps/Bourns/китайский нонейм) на один щелчок будет именно 4 импульса. Хотя мне попадались странные энкодеры, выдававшие разное количество импульсов на щелчок (то 4, то 5) - видимо, такое качество изготовления. Ещё попадались энкодеры, у которых восходящие фронты разнесены по времени, как это и надо, а падающие происходят практически одновременно. В принципе, когда таймер затактирован очень высокой частотой (как в примере выше - прямо от PCLK1), он всё равно "чует" разницу моментов между ними, но при снижении частоты неизбежно ухудшается временное разрешение и такие энкодеры становятся неприменимы.

Для самого простого применения крутилки в качестве интерфейсного органа нужен будет ещё один таймер. Этим вторым таймером мы будем формировать периоды накопления импульсов, чтобы обновлять некую переменную, связанную с положением энкодера. Можно и напрямую с регистром CNT работать, но это не слишком неудобно, т.к. нет никакого автоматического способа узнать, что это значение изменилось. Поэтому заводим ещё один таймер (например, TIM14), чтобы он тикал с какой-то небольшой частотой порядка 5..10 герц. По его прерыванию будем заглядывать в регистр TIM4->CNT. Если его текущее значение отличается от предыдущего замера, вычисляем разницу (в целых со знаком) и прибавляем её к аккумулятору ("value"). Тут же выставляем флажок о том, что энкодер обновился.

То, что в инициализации таймера в регистр TIM4->ARR было вписано "-1", помогает упростить арифметику - не нужно специально следить за переходом через границы и т.п., это происходит как бы само собой из-за того, что типы переменных чётко определены.

{ // Проверка таймера TIM4 (Enc0)
	IncEnc_t *enc = (IncEnc_t *)&gFlags.Enc[0];
	enc->prev_reg = enc->reg;
	enc->reg = TIM4->CNT;
	enc->diff = (enc->reg - enc->prev_reg);
	if (enc->diff != 0) {
		enc->dir = (TIM4->CR1 & TIM_CR1_DIR) ? 1 : 0;
		enc->value -= enc->diff; /* += или -= зависит от того, как мы хотим отобразить 
				физическое направление на направление счёта */
		enc->updated = 1;
	};
};

Здесь gFlags это некая структура, в которую вынесены глобальные переменные, мне кажется этот подход несколько более удобным для отладки (в дебагере можно смотреть сразу на всю структуру, не вынося отдельные переменные в отдельные записи watchlist). В эту структуру вложен массив других структур, хранящих инфу уже конкретно об энкодерах:
typedef struct {
	uint16_t reg, prev_reg;
	int16_t diff;
	uint8_t updated:1, dir:1;
	int32_t value;
	int32_t min_value, max_value;
	// ToDo: callback
} IncEnc_t;


Вместо флажка updated можно, наверное, вызывать какой-то коллбэк. Если энкодер предназначен строго для одной роли, то так будет, наверное, проще. Если же его назначение меняется (например, это какая-то крутилка, которая в одном режиме управляет громкостью аудио, в другом она помогает ползать по меню, а в третьем она регулирует какой-нибудь слайдер), то каждый раз подменять коллбэк может быть излишней работой.
Tags: радио
Subscribe

  • Абсолютли!

  • Мета

    Канал о фейлах .. эхм.. как бы это.. сам.

  • Неужели

    "Протон", который уже давно заменён на "Ангару" и больше не летает, сегодня должен вытащить "Науку", пролежавшую более 20 лет на Земле, и…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 2 comments