Страница 1 из 1

STM32 сенсорные кнопки

Добавлено: Чт дек 26, 2013 14:34:12
GARMIN
Делаю я тут клавиатуру для прибора. И грустно мне стало использовать мембранную клавиатуру, когда в моём микропроцессоре есть нативный контроллер сенсорных кнопок.
Информации по этому вопросу довольно мало. С одной стороны, есть библиотека от ST. Но она громоздкая и для моих задач излишняя.
С другой стороны, есть возможность прямо работать с регистрами контроллера. Нашёл в сети маленький примерчик, и на его основе делаю свой интерфейс.
Выкладываю наработки.
Условия: работа во FreeRTOS, процессор STM32F051.
Код пока не проверен, так как плата с сенсорными кнопками в процессе изготовления.
Инициализация:

Код: Выделить всё

void __inline vInit_TSC (void)
{	
// включаю тактирование блока сенсорных кнопок
	RCC->AHBENR |= RCC_AHBENR_TSEN | RCC_AHBENR_GPIOAEN;	

// настройка портов IO
	GPIOA->MODER |= GPIO_MODER_MODER4_1;		// PA4 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_4;			// пин PA4 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR4;			// пин без подтяжки

	GPIOA->MODER |= GPIO_MODER_MODER5_1;		// PA5 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_5;			// пин PA5 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;			// пин без подтяжки

	GPIOA->MODER |= GPIO_MODER_MODER6_1;		// PA6 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_6;			// пин PA6 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;			// пин без подтяжки

	GPIOA->MODER |= GPIO_MODER_MODER7_1;		// PA7 - вход конденсатора
	GPIOA->OTYPER |= GPIO_OTYPER_OT_7;			// пин PA7 - открытый сток
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;			// пин без подтяжки

	GPIOB->MODER |= GPIO_MODER_MODER11_1;		// PB11 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_11;		// пин PB11 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR11;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR11;		// пин без подтяжки

	GPIOB->MODER |= GPIO_MODER_MODER12_1;		// PB12 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_12;		// пин PB12 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR12;		// пин без подтяжки

	GPIOB->MODER |= GPIO_MODER_MODER13_1;		// PB13 - вход кнопки
	GPIOA->OTYPER &= ~GPIO_OTYPER_OT_13;		// пин PB13 - пуш пулл
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR13;		// пин без подтяжки

	GPIOB->MODER |= GPIO_MODER_MODER14_1;		// PB14 - вход конденсатора
	GPIOA->OTYPER |= GPIO_OTYPER_OT_14;			// пин PB14 - открытый сток
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR14;	// скорость пина 50 МГц
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR14;		// пин без подтяжки
	
	GPIOA->AFR[0] |= GPIO_AFRL_AFRL4 & (GPIO_AF_AF3 << 16);		// PA4 - слайдер 1
	GPIOA->AFR[0] |= GPIO_AFRL_AFRL5 & (GPIO_AF_AF3 << 20);		// PA5 - слайдер 2
	GPIOA->AFR[0] |= GPIO_AFRL_AFRL6 & (GPIO_AF_AF3 << 24);		// PA6 - слайдер 3
	GPIOA->AFR[0] |= GPIO_AFRL_AFRL7 & (GPIO_AF_AF3 << 28);		// PA7 - конденсатор
	GPIOB->AFR[1] |= GPIO_AFRH_AFRH3 & (GPIO_AF_AF3 << 12);		// PB11 - кнопка BACK
	GPIOB->AFR[1] |= GPIO_AFRH_AFRH4 & (GPIO_AF_AF3 << 16);		// PB12 - кнопка STOP
	GPIOB->AFR[1] |= GPIO_AFRH_AFRH5 & (GPIO_AF_AF3 << 20);		// PB13 - кнопка ENTER
	GPIOB->AFR[1] |= GPIO_AFRH_AFRH6 & (GPIO_AF_AF3 << 24);		// PB14 - конденсатор
	
// настройка контроллера сенсорных кнопок	
	TSC->CR = (1 << TSC_CR_TSCE);	// разрешить контроллер

	TSC->CR =
		  (2 << TSC_CR_CTPH)
		| (6 << TSC_CR_CTPL)
		| (9 << TSC_CR_SSD)
		| (0 << TSC_CR_SSE)
		| (1 << TSC_CR_SSPSC)
		| (0 << TSC_CR_PGPSC)
		| (TSC_CR_MCV_16383 << TSC_CR_MCV)
		| (0 << TSC_CR_IODEF)
		| (0 << TSC_CR_SYNCPOL)
		| (0 << TSC_CR_AM)
		| (0 << TSC_CR_START)
		| (1 << TSC_CR_TSCE);

	TSC->IER =
		  (1 << TSC_IER_EOAIE)		// прерывание по окончании измерения
		| (1 << TSC_IER_MCEIE);		// прерывание по переполнению

	TSC->IOSCR = TSC_IOSCR_G2_IO4	// конденсатор PA7 (G2_IO4) 
		| TSC_IOSCR_G6_IO4;			// конденсатор PB14 (G6_IO4)

	TSC->IOCCR = TSC_IOCCR_G2_IO1	// кнопка PA4
		| TSC_IOCCR_G6_IO1;			// кнопка PB11
//		| TSC_IOCCR_G2_IO2			// кнопка PA5
//		| TSC_IOCCR_G2_IO3			// кнопка PA6
//		| TSC_IOCCR_G6_IO1			// кнопка PB11
//		| TSC_IOCCR_G6_IO2			// кнопка PB12
//		TSC_IOCCR_G6_IO3;			// кнопка PB13
// в процессе измерения учавствует только одна кнопка из группы
		
	TSC->IOGCSR = TSC_IOGCSR_G2E	// разрешить группу 2
		| TSC_IOGCSR_G6E;			// разрешить группу 6
}
Я использовал 6 кнопок в двух группах. Видно по комментариям.
В обработчике прерывании я измеряю емкость всех кнопок, а затем передаю массив на обработку:

Код: Выделить всё

/*
Программа обработчика прерывания контроллера TS
Запускается снаружи установкой бита TSC->CR |= (1 << TSC_CR_START);
и при конфигурации разрешения первых кнопок в группах
Программа измеряет значения ёмкости всех шести кнопок в двух группах и
выдаёт результат в массиве. Значение ёмкости = 0 определяет ошибку измерения
Результат передаётся через очередь RTOS в обработчик клавиатуры
*/

void TS_IRQHandler (void)
{
	portBASE_TYPE 	xWoken = pdFALSE;
	Tsense 			*point_t = &t;
		
	if ((TSC->ISR & TSC_ISR_MCEF)					// переполнение счётчика
	{
		t.Cap[0][t.Position] = 0;					// записали ошибку измерения
		t.Cap[1][t.Position] = 0;
		TSC->ICR = TSC_ICR_MCEIC | TSC_ICR_EOAIC;	// снять флаг переполнения и окончания
	}
	else
	{		 
		t.Cap[0][t.Position] = *TS_Group[0];		// прочитали значение ёмкости
		t.Cap[1][t.Position] = *TS_Group[1];
		TSC->ICR = TSC_ICR_EOAIC;					// снять флаг окончания	
	}
		 
	if (++t.Position < TS_MAX_NUM_PIN)				// если измерили не все кнопки, продолжить
	{
		TSC->IOCCR = TS_Mask_Channel[0][t.Position] 
			| TS_Mask_Channel[0][t.Position];		// определить маску следующих кнопок
		TSC->CR |= (1 << TSC_CR_START);				// запустить измерение следующих кнопок
		t.EndMeasure = 0;							// сбросить флаг окончания
	}
	else											// если измерение окончено, передать структуру
	{
		t.Position = 0;								// подготовить указатель
		t.EndMeasure = 1;							// выставить флаг окончания
		xQueueSendToBackFromISR (g.xTS_Queue, &oint_t, &xWoken);
		portYIELD_FROM_ISR (xWoken);
	}
}
Структура данных кнопок:

Код: Выделить всё

#define	TS_MAX_NUM_GROUP	2
#define	TS_MAX_NUM_PIN	3

#pragma pack(4)
typedef struct Tsense
{
	int		Cap [TS_MAX_NUM_GROUP][TS_MAX_NUM_PIN];		// массив значений ёмкости кнопок
	int		Position;		// номер кнопки
	int		EndMeasure;		// флаг окончания измерения
}Tsense;
#pragma pack()

extern Tsense t;

// массив указателей на регистры счётчиков групп
const __IO uint32_t *TS_Group [TS_MAX_NUM_GROUP] = 
{
	&TSC->IOGXCR[1],
	&TSC->IOGXCR[5]	
};
// массив масок разрешения кнопок в группе
const uint32_t TS_Mask_Channel [TS_MAX_NUM_GROUP] [TS_MAX_NUM_PIN] =
{
	TSC_IOCCR_G2_IO1, TSC_IOCCR_G2_IO2, TSC_IOCCR_G2_IO3,
	TSC_IOCCR_G6_IO1, TSC_IOCCR_G6_IO2, TSC_IOCCR_G6_IO1	
};
И, наконец, главный цикл программы. Здесь пока рыба, а будет обработка

Код: Выделить всё

/*
Задача обработки значений сенсорных кнопок
*/		 
void vTsense (void *pvParameters)
{
	int 	i, j;
	Tsense	*Sense;
	int		TSC_min [TS_MAX_NUM_GROUP][TS_MAX_NUM_PIN];
	int		TSC_max [TS_MAX_NUM_GROUP][TS_MAX_NUM_PIN];
	
	vInit_TSC ();
	
// обнулить данные в структуре кнопок
	for (i = 0; i < TS_MAX_NUM_GROUP; i++)
	{
		for (j = 0; j < TS_MAX_NUM_PIN; j++)
		{
			TSC_max[i][j] = t.Cap[i][j] = 0;
			TSC_min[i][j] = 16384;				// максимальное значение
		}
	}
	t.Position = t.EndMeasure = 0;	// начальные значения указателей
	for ( ; ; )
	{
		vTaskDelay (200);
		TSC->CR |= (1 << TSC_CR_START);				// запустить измерение
// из очереди получаем указатель на структуру ёмкостей, ждём до посинения
		xQueueReceive (g.xTS_Queue, &Sense, portMAX_DELAY);
		if (t.EndMeasure)			// для случая небесконечного ожидания
		{
			t.EndMeasure = 0;	
			vTSCompareminmax (&Sense->Cap[0][0], TSC_min, TSC_max);
// для начала просто выведу значение емкости
		}
	}
}
Я выкладываю этот код скорее для того, чтобы самому лучше разобраться в процессе, но если кому интересно, пишите свои соображения.
Если кто заметит явные косяки, откликнитесь.
Также интересны способы постобработки для получения статуса нажатых кнопок и использования слайдера.

Re: STM32 сенсорные кнопки

Добавлено: Чт дек 26, 2013 18:05:13
coredumped
Дружище, очень интересен Ваш проект! Я делал touch клавиатуру на AT42QT2160, результат получился неплохой - работало через оргстекло 2мм. Пришлось немного повозиться с платой. Пишите все, что накопаете, интересно не столько программное решение, как ньюансы трассировки платы, а также материал и толщина панели. Просто у меня есть готовое решение, но хочется сделать "все-в-одном" - контроллер клавиатуры и LCD дисплея.
Заранее спасибо.

Re: STM32 сенсорные кнопки

Добавлено: Пн дек 30, 2013 22:57:26
GARMIN
Немного результатов.
Всё запустилось. Наступал на грабли. Как свои по невнимательности, так и послушался глупых советов от STM.
Сенсорные кнопки уверенно работают через стеклотекстолит 1,5мм со шлейфом 20 см на шилд Дискавери.
Показания счётчиков плывут от времени по причине нестабильности конденсаторов и геометрии шлейфа.
Применил фильтрацию сигналов - всё отлично заработало.
Пока реализован вариант с отдельными кнопками.
Вторым этапом буду мучить слайдер - хочу получить данные в виде линейного указателя 0-100%

Re: STM32 сенсорные кнопки

Добавлено: Пт янв 03, 2014 16:17:53
GARMIN
Поработал над ошибками. Результаты вполне стабильные.
Привожу картинку анализа работы программы обработки значений кнопок
Изображение
Я выводил текущие значения переменных в терминал, а затем анализировал в Экселе. Ну и подписал графики в графическом редакторе.
Самый верхний график - отфильтрованное максимальное значение счётчика контроллера кнопок. Максимальное значение получается при минимальной ёмкости кнопки.
Видно, как при нажатии на кнопку уменьшается значение счётчика - увеличивается ёмкость кнопки (через текстолит 1,5 мм)
У меня есть переменная, которая отслеживает максимальное изменение ёмкости кнопки. Относительно этой переменной определяется порог срабатывания счётчика антидребезга. Как только счётчик досчитал до определённой величины - изменяется состояние кнопки. Она считается нажатой. После отпускания также считает счётчик антидребезга отпускания. Если досчитал до порога - кнопка считается отжатой. Это стандартный антидребезг. А вот над функциями фильтрации сигналов и порогами срабатывания пришлось поэкспериментировать.
Чтобы остальные не наступали на самые грабельные грабли, подскажу один неявный момент:
У меня при измерении ёмкости трёх датчиков одного и того же канала был странный глюк. Первый канал давал стабильные показания, а два других прыгали случайным образом на 3-10%. После трёхдневных поисков выяснилось, что после измерения одного канала и переключения регистра маски канала

Код: Выделить всё

TSC->IOCCR = TS_Mask_Channel;
и перед следующим измерением

Код: Выделить всё

TSC->CR |= (1 << TSC_CR_START);

обязательно нужна задержка для разрядки конденсаторов.
Я просто применил цикл на 40 тактов.

Re: STM32 сенсорные кнопки

Добавлено: Пт янв 03, 2014 16:43:45
coredumped
Спасибо за опыт!

Re: STM32 сенсорные кнопки

Добавлено: Пт апр 11, 2014 11:45:56
evilbob
А можно взглянуть на саму схему?

Re: STM32 сенсорные кнопки

Добавлено: Вт апр 15, 2014 17:52:01
GARMIN
evilbob писал(а):А можно взглянуть на саму схему?
Схема из даташита, там два конденсатора и резисторы на ножках.