Подключение энкодера к STM32F103

Январь 2017 г.

Задача

Материалов по теме много, но почему-то большинство авторов предлагает для работы с энкодером использовать таймер. Это оправдано, когда нужно определять скорость вращения. Мне же просто требовалось знать: крутит ли пользователь ручку, и если да — в какую сторону.

Принцип действия

Энкодеры бывают разные, в частности: абсолютные и импульсные (инкрементные). У абсолютных много контактов, с которых можно считывать код, соответствующий углу поворота. У импульсных (наш случай): два выхода и земля. Если вращать вал энкодера, эти выходы будут выдавать импульсы одинаковой частоты, но смещённые по времени.

Например. Крутим по часовой стрелке: на землю замыкается сначала выход 0, потом — выход 1. Крутим против часовой: замыкается выход 1, потом — выход 0. Никуда не крутим: состояние выходов не меняется.

Технически грамотное описание принципа работы энкодеров можно найти в Википедии.

Подключение

B0, B1 — два выхода, упомянутые выше. B2 — кнопка, которая срабатывает, если нажать на ручку.

Подключение энкодера к микроконтроллеру

Рис. 1. Схема подключения энкодера.

Текст программы

Для примера рассмотрим управление уровнем громкости при помощи энкодера.

Исходя из принципа действия, нам нужно:

  1. Отреагировать на изменение состояния одного из выходов энкодера. Пусть это будет B0.
  2. Определить направление вращения, проверив состояние второго выхода — B1.
  3. При необходимости посчитать количество импульсов.

Проще всего это сделать при помощи прерываний.


#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"
#include "misc.h"				// Нужен для NVIC

// Максимальное значение уровня громкости
#define VOLUME_MAX_VAL	51

// Текущий уровень громкости
volatile uint8_t nVol = 10;



int main(void) {
    // Включаем тактирование модуля ввода-вывода 'B'
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// Переменные (структуры) для инициализации
	GPIO_InitTypeDef GPIO_InitStruct;
	EXTI_InitTypeDef EXTI_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;

    // Прерывания - это альтернативная функция порта, включаем
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

	// Настройка пинов B0 и B1
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;		// Вход с подтяжкой вверх
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);

	// Добавляем вектор прерывания
	NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
	// Устанавливаем приоритет
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x08;	// 0x00 - 0x0F
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x08;
	// Разрешаем прерывание
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);

	// PB0 подключен к EXTI_Line0
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
	EXTI_InitStruct.EXTI_Line = EXTI_Line0;
	// Разрешаем прерывание
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	// По спаду
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStruct);
	

	while(1) {
		// * * *
		}
}



// Обработчик прерывания для энкодера по спаду PB0
void EXTI0_IRQHandler(void) {
	uint32_t nCount = 3;

	// Убеждаемся, что флаг прерывания установлен
	if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
    	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1) {
			// Кто-то крутит ручку по часовой стрелке
    		if(nVol < VOLUME_MAX_VAL) {
    			nVol++;
    			}
        	}
    	else {
			// Ручку крутят против часовой стрелки
    		if(nVol > 0) {
    			nVol--;
    			}
    		}

		for(nCount *= 100000; nCount; nCount--);

		// Сбрасываем флаг прерывания
		EXTI_ClearITPendingBit(EXTI_Line0);
		}
}


 

Примечание: Если входные пины инициализированы с "подтяжкой вверх", использовать подтягивающие резисторы R1-R3 не обязательно.

Отслеживать нажатие кнопки можно аналогичным образом.

Среда разработки: КуКокс. (www.coocox.org/software/coide.php)

 

Если у вас есть комментарий по существу — присылайте.

Микроконтроллеры   1999-2017 © Advertising and Research