Продолжаем наш амбициозный «Hello, World!» на отладочной плате Atmel SAMD21 Xplained, затеянный в первой части, в которой была описана работа с Wi-Fi модулем WINC1500.
Сегодня будет продемонстрирован пример обработки сенсорных кнопок и слайдера при помощи библиотеки Q-touch.
В третьей части цикла, как и было обещано, данные с этих сенсоров будут «запаковываться» в посылку ModBus TCP и передаваться по Wi-Fi в систему управления освещением в нашем офисе.
Для начала, разберемся что же это за Q-touch. Это атмеловская реализация технологии обработки сенсорных резистивных кнопок и слайдеров, сопровождаемая библиотекой для упрощения работы с ними. Причем во всех микроконтроллерах SAMD реализован аппаратный контроллер Q-touch (так называемый периферийный контроллер прикосновений (PTC)). Он позволяет как минимизировать число использованных выводов микроконтроллера, так и нагрузку на вычислительное ядро.
В качестве сенсоров будем использовать модуль расширения ATQT1-XPRO, который, как уже упоминалось в прошлой статье, может быть установлен на любую отладочную плату из серии Xplained Pro.
Технология Qtouch поддерживает следующие виды сенсоров: кнопки, слайдеры, роторы и определение приближения (proximity).
Технологии Qtouch и QMatrix
QTouch основана на измерении собственной емкости, а QMatrix на измерении совместной.
Измерение с использованием собственной емкости подразумевает заряд чувствительного электрода неизвестной емкости до известного потенциала. Результирующий заряд передается в измерительную цепь. С помощью циклов заряда-и-передачи можно измерить емкость чувствительной пластины.
Измерение с использованием совместной емкости осуществляется с помощью двух электродов. Один из электродов выступает в качестве эмиттера, который принимает заряд, который передается логическими импульсами в последовательном (burst) режиме. Второй электрод выступает как получатель, который связывается с эмиттером через диэлектрик, из которого сделана тач-панель. Когда палец касается панели, совместное поле уменьшается, и прикосновение определяется.
Qtouch | QMatrix |
---|---|
Собственная емкость | Совместная емкость |
Надежный и простой дизайн электродов | Хорошо определенная область детектирования нажатия |
Идеальна для небольшого количества сенсоров | Идеально для большого количества сенсоров (больше 10) |
Хорошее определение приближения, на большем расстоянии | Хорошо приспосабливающаяся к влажности и окружающей среде |
Теоретически возможна любая форма электрода | Пассивное отслеживание – возможны более длинные пути |
Легко настраивается чувствительность | Хорошо приспосабливающаяся к шуму и наводкам по земле |
В контроллерах серии SAMD20 и 21 технология QTouch/QMatrix встроена. Специальный блок, который за нее отвечает – это периферийный контроллер прикосновений (PTC). Схема работы всей системы показана на рисунке ниже.
Создаем проект
Для освоения новой периферии очень удобно пользоваться проектами-примерами. А для QTouch есть еще специальный плагин QTouch Composer, который делает разработку визуальной. Но если необходимо встроить сенсорные кнопки в уже существующий проект, надо понимать всю последовательность действий и настройки. Сейчас этим и займемся.
Общая схема работы библиотеки приведена на блок схеме:
Добавляем в проект с помощью визарда PTC и RTC.
Конфигурация настраивается в файле touch_config_samd.h. Пройдемся по основным параметрам.
Сначала необходимо выбрать способ определения прикосновений: собственная емкость или совместная. Выбор осуществляется с помощью задания значений соответствующих констант.
#define DEF_TOUCH_MUTLCAP (1u)
#define DEF_TOUCH_SELFCAP (0u)
Приоритет прерываний от PTC контроллера о завершении преобразования может иметь значения от 0 до 3 (0 самый высокий приоритет). И устанавливается с помощью дефайна:
#define DEF_TOUCH_PTC_ISR_LVL (1u)
Как уже упоминалось выше, для определения прикосновения с помощью совместной емкости необходимо две линии: X и Y. У samd21 16 линий X и Y. В данном случае (использования отладки с платой расширения) у нас нет выбора на какую пару линий какую кнопку/слайдер/ротор заводить. Порядок указания пар выводов задает номера каналов. Для ротора/слайдера обязательно использовать одну и ту же линию Y для всех каналов. Указание линий осуществляется с помощью соответствующего дефайна:
#define DEF_MUTLCAP_NODES X(8), Y(10), X(9), Y(10), X(2), Y(12), X(3), Y(12),
X(8), Y(12), X(9), Y(12), X(2), Y(13), X(3), Y(13),
X(8), Y(13), X(9), Y(13)
Указываем количество каналов (для кнопки всегда 1 канал, для ротора/слайдера от 3 до 8). В нашем случае используется 2 кнопки и по 4 канала на ротор и слайдер, всего 10 каналов:
#define DEF_MUTLCAP_NUM_CHANNELS (10) /* Total number of channels */
Указываем количество сенсоров (у нас 2 кнопки, один слайдер и один ротор итого 4):
#define DEF_MUTLCAP_NUM_SENSORS (4) /* Total number of sensors */
Указываем количество роторов/сенсоров (у нас один слайдер и один ротор итого 2):
#define DEF_MUTLCAP_NUM_ROTORS_SLIDERS (2) /* Number of rotor sliders */
Переходим к указанию параметров преобразования.
Уровень фильтрации влияет на точность и скорость преобразования. Чем выше уровень (от 1 до 64), тем больше семплов приходится на 1 преобразование, что улучшает соотношение шум/сигнал, но увеличивает время преобразования.
#define DEF_MUTLCAP_FILTER_LEVEL FILTER_LEVEL_32 /* Filter level */
Усиление сигнала с сенсоров настраивается поканально. Диапазон значений от 1 до 32.
#define DEF_MUTLCAP_GAIN_PER_NODE GAIN_1, GAIN_1, GAIN_1, GAIN_1, GAIN_1,
GAIN_1, GAIN_1, GAIN_1, GAIN_1, GAIN_1
Устанавливаем период опроса в миллисекундах.
#define DEF_TOUCH_MEASUREMENT_PERIOD_MS 20u
Как и для механических кнопок, для сенсорных предусмотрен своеобразный антидребезг. Он заключается в том, что вы указываете в течении скольки циклов измерения уровень сигнала должен превышать пороговый для детектирования прикосновения к кнопке/ротору/слайдеру.
#define DEF_MUTLCAP_DI 4u
Бывает так, что какой-то предмет долго касается сенсора. В таком случае необходимо через некоторое время перекалибровать сенсор с учетом новых условий работы. Для установки времени, через которое происходит перекалибровка, используется специальная константа. Время устанавливается в единицах 200 мс (т.е. значение 5 соответствует 1 сек). Если время установить в 0, то автоматическая рекалибровка проводиться не будет.
#define DEF_MUTLCAP_MAX_ON_DURATION 0u
Можно разрешить или запретить вывод отладочный информации для Qtouch Analyzer:
<cut />#define DEF_TOUCH_QDEBUG_ENABLE 0u
По каким-то своим странным соображениям Atmel не включил стандартных функций инициализации PTC (как с другой периферией) и определение нескольких необходимых констант. Поэтому это все надо делать самостоятельно. Чем мы сейчас и займемся.
Прежде всего нам необходимо инициализировать RTC, так как по прерываниям от него будут проверяться срабатывания кнопок в нашем случае. Настраиваем RTC, регистрируем callback, пишем код для callback. RTC будет генерировать прерывания раз в 1 мсек, если прошло столько мсек, сколько у нас интервал между считыванием кнопок, то выставляем соответствующий флаг, который будет проверяться в main.
Необходимые объявления:
// RTC Interrupt timing definition
#define TIME_PERIOD_1MSEC 33u
/* ! QTouch Library Timing info. */
touch_time_t touch_time;
volatile uint16_t touch_time_counter = 0u;
struct rtc_module rtc_instance;
Необходимые функции:
void rtc_overflow_callback(void)
{
/* Do something on RTC overflow here */
if(touch_time_counter == touch_time.measurement_period_ms)
{
touch_time.time_to_measure_touch = 1u;
touch_time.current_time_ms = touch_time.current_time_ms +
touch_time.measurement_period_ms;
touch_time_counter = 0u;
}
else
{
touch_time_counter++;
}
}
void configure_rtc_callbacks(void)
{
/* register callback */
rtc_count_register_callback(&rtc_instance, rtc_overflow_callback, RTC_COUNT_CALLBACK_OVERFLOW);
/* Enable callback */
rtc_count_enable_callback(&rtc_instance,RTC_COUNT_CALLBACK_OVERFLOW);
}
void configure_rtc_count(void)
{
struct rtc_count_config config_rtc_count;
rtc_count_get_config_defaults(&config_rtc_count);
config_rtc_count.prescaler = RTC_COUNT_PRESCALER_DIV_1;
config_rtc_count.mode = RTC_COUNT_MODE_16BIT;
config_rtc_count.continuously_update = true;
/* initialize rtc */
rtc_count_init(&rtc_instance,RTC,&config_rtc_count);
/* enable rtc */
rtc_count_enable(&rtc_instance);
}
void timer_init(void)
{
/* Configure and enable RTC */
configure_rtc_count();
/* Configure and enable callback */
configure_rtc_callbacks();
/* Set Timer Period */
rtc_count_set_period(&rtc_instance,TIME_PERIOD_1MSEC);
}
Теперь нужно настроить сам PTC. Сначала добавляем необходимые структуры:
static touch_mutlcap_config_t mutlcap_config = {
DEF_MUTLCAP_NUM_CHANNELS, /* Mutual Cap number of channels. */
DEF_MUTLCAP_NUM_SENSORS, /* Mutual Cap number of sensors. */
DEF_MUTLCAP_NUM_ROTORS_SLIDERS, /* Mutual Cap number of rotors and sliders. */
/* Mutual Cap GLOBAL SENSOR CONFIGURATION INFO. */
{
DEF_MUTLCAP_DI, /* uint8_t di; Sensor detect integration (DI) limit. */
/* Interchanging Negative and Positive Drift rate, since Signal increases on Touch. */
DEF_MUTLCAP_ATCH_DRIFT_RATE, /* uint8_t neg_drift_rate; Sensor negative drift rate. */
DEF_MUTLCAP_TCH_DRIFT_RATE, /* uint8_t pos_drift_rate; Sensor positive drift rate. */
DEF_MUTLCAP_MAX_ON_DURATION, /* uint8_t max_on_duration; Sensor maximum on duration. */
DEF_MUTLCAP_DRIFT_HOLD_TIME, /* uint8_t drift_hold_time; Sensor drift hold time. */
DEF_MUTLCAP_ATCH_RECAL_DELAY, /* uint8_t pos_recal_delay; Sensor positive recalibration delay. */
DEF_MUTLCAP_CAL_SEQ1_COUNT,
DEF_MUTLCAP_CAL_SEQ2_COUNT,
DEF_MUTLCAP_ATCH_RECAL_THRESHOLD, /* recal_threshold_t recal_threshold; Sensor recalibration threshold. */
},
{
mutlcap_gain_per_node, /* Mutual Cap channel gain setting. */
DEF_MUTLCAP_FREQ_MODE, /* Mutual Cap noise counter measure enable/disable. */
DEF_MUTLCAP_CLK_PRESCALE,
DEF_MUTLCAP_SENSE_RESISTOR,
DEF_MUTLCAP_CC_CAL_CLK_PRESCALE,
DEF_MUTLCAP_CC_CAL_SENSE_RESISTOR,
mutlcap_freq_hops,
DEF_MUTLCAP_FILTER_LEVEL, /* Mutual Cap filter level setting. */
DEF_MUTLCAP_AUTO_OS, /* Mutual Cap auto oversamples setting.*/
},
mutlcap_data_blk, /* Mutual Cap data block index. */
PRIV_MUTLCAP_DATA_BLK_SIZE, /* Mutual Cap data block size. */
mutlcap_xy_nodes, /* Mutual Cap channel nodes. */
DEF_MUTLCAP_QUICK_REBURST_ENABLE,
DEF_MUTLCAP_FILTER_CALLBACK /* Mutual Cap filter callback function pointer. */
};
touch_config_t touch_config = {
&mutlcap_config, /* Pointer to Mutual Cap configuration structure. */
NULL,
DEF_TOUCH_PTC_ISR_LVL, /* PTC interrupt level. */
};
Макросы:
#define GET_MUTLCAP_SENSOR_STATE(SENSOR_NUMBER) p_mutlcap_measure_data->
p_sensor_states[(SENSOR_NUMBER /
8)] & (1 << (SENSOR_NUMBER % 8))
Дефайны:
#define DEF_MUTLCAP_CAL_SEQ1_COUNT 8
#define DEF_MUTLCAP_CAL_SEQ2_COUNT 4
#define DEF_MUTLCAP_CC_CAL_CLK_PRESCALE PRSC_DIV_SEL_8
#define DEF_MUTLCAP_CC_CAL_SENSE_RESISTOR RSEL_VAL_100
#define DEF_MUTLCAP_QUICK_REBURST_ENABLE 1u
#define PTC_APBC_BITMASK (1u << 19u)
Переменные:
static uint8_t mutlcap_data_blk[PRIV_MUTLCAP_DATA_BLK_SIZE];
uint16_t mutlcap_xy_nodes[DEF_MUTLCAP_NUM_CHANNELS * 2] = {DEF_MUTLCAP_NODES};
gain_t mutlcap_gain_per_node[DEF_MUTLCAP_NUM_CHANNELS]= {DEF_MUTLCAP_GAIN_PER_NODE};
freq_hop_sel_t mutlcap_freq_hops[3u] = {DEF_MUTLCAP_HOP_FREQS};
Функция конфигурации тактирования PTC:
void touch_configure_ptc_clock(void)
{
struct system_gclk_chan_config gclk_chan_conf;
system_gclk_chan_get_config_defaults(&gclk_chan_conf);
gclk_chan_conf.source_generator = GCLK_GENERATOR_3;
system_gclk_chan_set_config(PTC_GCLK_ID, &gclk_chan_conf);
system_gclk_chan_enable(PTC_GCLK_ID);
system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, PTC_APBC_BITMASK);
}
Конфигурация сенсоров:
touch_ret_t touch_sensors_config(void)
{
touch_ret_t touch_ret = TOUCH_SUCCESS;
sensor_id_t sensor_id;
touch_ret = touch_mutlcap_sensor_config(SENSOR_TYPE_KEY, CHANNEL_0,
CHANNEL_0, NO_AKS_GROUP, 20u,
HYST_6_25, RES_8_BIT,0,
&sensor_id);
if (touch_ret != TOUCH_SUCCESS) while (1);
touch_ret = touch_mutlcap_sensor_config(SENSOR_TYPE_KEY, CHANNEL_1,
CHANNEL_1, NO_AKS_GROUP, 20u,
HYST_6_25, RES_8_BIT,0,
&sensor_id);
if (touch_ret != TOUCH_SUCCESS) while (1);
touch_ret = touch_mutlcap_sensor_config(SENSOR_TYPE_ROTOR, CHANNEL_6,
CHANNEL_9, NO_AKS_GROUP, 20u,
HYST_6_25, RES_8_BIT,0,
&sensor_id);
if (touch_ret != TOUCH_SUCCESS) while (1);
touch_ret = touch_mutlcap_sensor_config(SENSOR_TYPE_SLIDER, CHANNEL_2,
CHANNEL_5, NO_AKS_GROUP, 20u,
HYST_6_25, RES_8_BIT,0,
&sensor_id);
if (touch_ret != TOUCH_SUCCESS) while (1);
return (touch_ret);
}
touch_ret_t touch_sensors_init(void)
{
touch_ret_t touch_ret = TOUCH_SUCCESS;
/* Setup and enable generic clock source for PTC module. */
touch_configure_ptc_clock();
touch_time.measurement_period_ms = DEF_TOUCH_MEASUREMENT_PERIOD_MS;
/* Initialize touch library for Mutual Cap operation. */
touch_ret = touch_mutlcap_sensors_init(&touch_config);
if (touch_ret != TOUCH_SUCCESS)
{
while (1u); /* Check API Error return code. */
}
#if DEF_TOUCH_QDEBUG_ENABLE == 1
QDebug_Init();
#endif
/* configure the touch library sensors. */
touch_ret = touch_sensors_config();
if (touch_ret != TOUCH_SUCCESS)
{
while (1u); /* Check API Error return code. */
}
/* Auto Tuning setting for calibration.
*
* AUTO_TUNE_PRSC: When Auto tuning of pre-scaler is selected
* the PTC uses the user defined internal series resistor setting
* (DEF_MUTLCAP_SENSE_RESISTOR) and the pre-scaler is adjusted
* to slow down the PTC operation to ensure full charge transfer.
*
* AUTO_TUNE_RSEL: When Auto tuning of the series resistor is
* selected the PTC runs at user defined pre-scaler setting speed
* (DEF_MUTLCAP_CLK_PRESCALE) and the internal series resistor is
* tuned automatically to the optimum value to allow for full
* charge transfer.
*
* AUTO_TUNE_NONE: When manual tuning option is selected (AUTO_TUNE_NONE),
* the user defined values of PTC pre-scaler and series resistor is used
* for PTC operation as given in DEF_MUTLCAP_CLK_PRESCALE and
* DEF_MUTLCAP_SENSE_RESISTOR
*
*/
touch_ret = touch_mutlcap_sensors_calibrate(AUTO_TUNE_RSEL);
if (touch_ret != TOUCH_SUCCESS)
{
while (1u); /* Check API Error return code. */
}
return (touch_ret);
}
void touch_mutlcap_measure_complete_callback( void )
{
#if DEF_TOUCH_QDEBUG_ENABLE == 1
/* Send out the Touch debug information data each time when Touch
* measurement process is completed .
* The Touch Signal and Touch Delta values are always sent.
* Touch Status change, Rotor-Slider Position change and Sensor
* Reference
* values can be optionally sent using the masks below.
*/
QDebug_SendData( TOUCH_CHANNEL_REF_CHANGE |
TOUCH_ROTOR_SLIDER_POS_CHANGE |
TOUCH_STATUS_CHANGE );
/* QT600 two-way QDebug communication application Example. */
/* Process any commands received from QTouch Studio. */
QDebug_ProcessCommands();
#endif
if (!(p_mutlcap_measure_data->acq_status & TOUCH_BURST_AGAIN))
{
/* Set the Mutual Cap measurement done flag. */
p_mutlcap_measure_data->measurement_done_touch = 1u;
}
}
touch_ret_t touch_sensors_measure(void)
{
touch_ret_t touch_ret = TOUCH_SUCCESS;
if (touch_time.time_to_measure_touch == 1u)
{
/* Start a touch sensors measurement process. */
touch_ret = touch_mutlcap_sensors_measure(
touch_time.current_time_ms,
NORMAL_ACQ_MODE,
touch_mutlcap_measure_complete_callback);
if ((touch_ret != TOUCH_ACQ_INCOMPLETE) && (touch_ret == TOUCH_SUCCESS))
{
touch_time.time_to_measure_touch = 0u;
}
else if ((touch_ret != TOUCH_SUCCESS) &&(touch_ret != TOUCH_ACQ_INCOMPLETE))
{
while (1);
/* Reaching this point can be due to -
* 1. The api has retured an error due to a invalid
* input parameter.
* 2. The api has been called during a invalid Touch
* Library state. */
}
}
return (touch_ret);
}
В main необходимо инициализировать таймер RTC, инициализировать PTC, настроить sleep режим (по необходимости), и разрешить глобальные прерывания:
//Initialize timer. (RTC actually
timer_init();
//Initialize QTouch library and configure touch sensors.
touch_sensors_init();
NVMCTRL->CTRLB.bit.SLEEPPRM = 3;
system_set_sleepmode(SYSTEM_SLEEPMODE_STANDBY);
Пусть в простейшем случае у нас индицируется светодиодами прикосновение к кнопке и позиция слайдера. Ротор трогать не будем.
В while(1) необходимо добавить функцию засыпания (при необходимости), функцию обработки прикосновения и зажигания соответствующих светодиодов для индикации прикосновения:
// Goto STANDBY sleep mode, unless woken by timer or PTC interrupt.
system_sleep();
// Start touch sensor measurement, if touch_time.time_to_measure_touch flag is set by timer.
touch_sensors_measure();
if ((p_mutlcap_measure_data->measurement_done_touch == 1u))
{
p_mutlcap_measure_data->measurement_done_touch = 0u;
// Get touch sensor states
button1_state = GET_MUTLCAP_SENSOR_STATE(0);
button2_state = GET_MUTLCAP_SENSOR_STATE(1);
rotor_state = GET_MUTLCAP_SENSOR_STATE(2);
slider_state = GET_MUTLCAP_SENSOR_STATE(3);
if (button1_state)
{
if(button_pressed!=1)
{
port_pin_set_output_level(LED_8_PIN, 0);
button_pressed=1;
}
}
else
{
port_pin_set_output_level(LED_8_PIN, 1);
if (button_pressed==1)
{
button_pressed=0;
}
}
if (button2_state)
{
if(button_pressed!=2)
{
port_pin_set_output_level(LED_9_PIN, 0);
button_pressed=2;
}
}
else
{
port_pin_set_output_level(LED_9_PIN, 1);
if (button_pressed==2)
{
button_pressed=0;
}
}
// Clear all slider controlled LEDs
port_pin_set_output_level(LED_0_PIN, 1);
port_pin_set_output_level(LED_1_PIN, 1);
port_pin_set_output_level(LED_2_PIN, 1);
port_pin_set_output_level(LED_3_PIN, 1);
port_pin_set_output_level(LED_4_PIN, 1);
port_pin_set_output_level(LED_5_PIN, 1);
port_pin_set_output_level(LED_6_PIN, 1);
port_pin_set_output_level(LED_7_PIN, 1);
// If slider is activated
if(slider_state)
{
// Parse slider position
slider_position = GET_MUTLCAP_ROTOR_SLIDER_POSITION(1);
slider_position = slider_position >> 5u;
switch(slider_position)
{
case 0:
port_pin_set_output_level(LED_0_PIN, 0);
break;
case 1:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
break;
case 2:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
break;
case 3:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
port_pin_set_output_level(LED_3_PIN, 0);
break;
case 4:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
port_pin_set_output_level(LED_3_PIN, 0);
port_pin_set_output_level(LED_4_PIN, 0);
break;
case 5:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
port_pin_set_output_level(LED_3_PIN, 0);
port_pin_set_output_level(LED_4_PIN, 0);
port_pin_set_output_level(LED_5_PIN, 0);
break;
case 6:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
port_pin_set_output_level(LED_3_PIN, 0);
port_pin_set_output_level(LED_4_PIN, 0);
port_pin_set_output_level(LED_5_PIN, 0);
port_pin_set_output_level(LED_6_PIN, 0);
break;
case 7:
port_pin_set_output_level(LED_0_PIN, 0);
port_pin_set_output_level(LED_1_PIN, 0);
port_pin_set_output_level(LED_2_PIN, 0);
port_pin_set_output_level(LED_3_PIN, 0);
port_pin_set_output_level(LED_4_PIN, 0);
port_pin_set_output_level(LED_5_PIN, 0);
port_pin_set_output_level(LED_6_PIN, 0);
port_pin_set_output_level(LED_7_PIN, 0);
break;
default:
port_pin_set_output_level(LED_0_PIN, 1);
port_pin_set_output_level(LED_1_PIN, 1);
port_pin_set_output_level(LED_2_PIN, 1);
port_pin_set_output_level(LED_3_PIN, 1);
port_pin_set_output_level(LED_4_PIN, 1);
port_pin_set_output_level(LED_5_PIN, 1);
port_pin_set_output_level(LED_6_PIN, 1);
port_pin_set_output_level(LED_7_PIN, 1);
break;
}
}
}//measurement done flag
Компилируем, заливаем, наслаждаемся.
Автор: Rainbow