Делаю я тут клавиатуру для прибора. И грустно мне стало использовать мембранную клавиатуру, когда в моём микропроцессоре есть нативный контроллер сенсорных кнопок.
Информации по этому вопросу довольно мало. С одной стороны, есть библиотека от 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);
// для начала просто выведу значение емкости
}
}
}
Я выкладываю этот код скорее для того, чтобы самому лучше разобраться в процессе, но если кому интересно, пишите свои соображения.
Если кто заметит явные косяки, откликнитесь.
Также интересны способы постобработки для получения статуса нажатых кнопок и использования слайдера.