Сенсорные кнопки с переносом заряда на STM8L
Сенсорные кнопки с переносом заряда имеют
хорошую помехозащищённость и чувствительность, но более сложны в реализации. На
универсальных микроконтроллерах (на примере STM8S серии) вполне возможно получить такие кнопки. Такая реализация описана
в этой статье. Для каждой кнопки нужно использовать два вывода микроконтроллера
и один накопительный конденсатор. Эту схему успешно повторили на
микроконтроллерах ATTiny, ATMega. В линейке продуктов фирмы STM есть большое количество
контроллеров, поддерживающих сенсорные кнопки как на аппаратном, так и на
программном уровне. Однако, для их реализации нужен накопительный конденсатор
на каждую группу выводов (от 1-й до 3-х сенсорных кнопок), и размер библиотеки
совершенно не оптимальный для мелких контроллеров. Используя систему внутренней
коммутации Routing Interface (RI) и программную реализацию, я построил сенсорные кнопки с одним
накопительным конденсатором на контроллер. Это позволило освободить выводы для
других целей в небольшом двадцатиногом корпусе.
Мой проект построен на микроконтроллере STM8L051F3 в корпусе TSSOP-20, но может
быть реализован на любом контроллере этой серии, в котором есть блок RI.
Схема соединений сенсорных кнопок:
Это
кусочек схемы моей сенсорной лампы, для примера. C4 как
раз и есть накопительный конденсатор, а S1-S3сенсорные
кнопки. Резисторы R11-R13 служат для защиты выводов МК от статического электричества.
Внутренние соединения выделены на следующем рисунке:
Здесь использован рисунок 25 из документа RM0031 (reference manual for STM8L series).
Основные определения:
Как видите, используя внутренние ключи и управление выводами, можно заряжать сенсорные датчики и передавать их заряд накопительному конденсатору. Алгоритм работы следующий:
1) Разрядить накопительный конденсатор,
2) Зарядить сенсорный датчик, подав на него напряжение питания через вывод микроконтроллера,
3) Соединить сенсорный датчик с конденсатором и дать заряду датчика перетечь в накопительный конденсатор,
4) Проверить напряжение на конденсаторе. Если достигло порогового уровня – закончить измерение, если нет – повторить с пункта 1.
5) Сменить канал измерения и повторить с пункта 1.
6) После измерения ёмкости всех датчиков подождать несколько миллисекунд.
Ёмкость сенсорного датчика обратно пропорциональна количеству циклов зарядки. Я использую количество циклов напрямую, хотя можно вычислять и ёмкость датчиков в пикофарадах.
Программа измерения ёмкости состоит из двух частей:
a) Инициализации
b) Программы конечного автомата, вызываемой из бесконечного основного цикла.
В программе также использована подсистема времени на основе миллисекундного таймера.
1) Разрядить накопительный конденсатор,
2) Зарядить сенсорный датчик, подав на него напряжение питания через вывод микроконтроллера,
3) Соединить сенсорный датчик с конденсатором и дать заряду датчика перетечь в накопительный конденсатор,
4) Проверить напряжение на конденсаторе. Если достигло порогового уровня – закончить измерение, если нет – повторить с пункта 1.
5) Сменить канал измерения и повторить с пункта 1.
6) После измерения ёмкости всех датчиков подождать несколько миллисекунд.
Ёмкость сенсорного датчика обратно пропорциональна количеству циклов зарядки. Я использую количество циклов напрямую, хотя можно вычислять и ёмкость датчиков в пикофарадах.
Программа измерения ёмкости состоит из двух частей:
a) Инициализации
b) Программы конечного автомата, вызываемой из бесконечного основного цикла.
В программе также использована подсистема времени на основе миллисекундного таймера.
Основные определения:
typedef void (*mess_func) (void);
typedef u8 (*pos_func) (void);
typedef bool (*cap_func) (void);
typedef void (*config_func) (void);
// объявления структур
--------------------------------------------------------
typedef enum _CH_states
{
CH_INIT = 0,
CH_UNPRESS,
CH_FOLLOW,
CH_FOLLOW_LONG
} CH_states; //
определение состояния клавиш
typedef enum _cap_status
{
C_INIT = 0,
C_UNPRESS,
C_FOLLOW,
C_FOLLOW_LONG
} cap_status;
typedef enum touch_modes
{
T_CONFIG = 0,
T_COUNT,
T_DISCHARGE,
T_WAIT
} touch_modes;
typedef struct _t_button
{
u8 cap; // текущие данные ёмкости кнопки
u16 acc; // Аккумулятор для фильтра НЧ
u8 flt; // отфильтрованные данные ёмкости пустой
кнопки
u8 calib; // максимальные значения ёмкости датчиков для
калибровки уровней
u8 level; // уровень нажатия кнопки 0-255
config_func config; // функция инициализации ёмкостного датчика
cap_func func; //
функция обслуживания ёмкостного датчика
} t_button;
typedef struct _t_slider_3_button
{
t_button s[3]; // сенсорные датчики
u8
s_count; //
счётчик окончания измерения ёмкостных датчиков
cap_status st; // состояние измерителя слайдера
touch_modes mode; // режим работы слайдера
u16 timer_decrease; // таймер адаптации к помехам
u16 debounce; //
таймер антидребезга
u16 timeout; //
таймер антизалипания
} t_slider_3_button;
static t_slider_3_button T;
Инициализация выводов:
/******************************************************************************
* инициализация обработчика сенсорных кнопок
******************************************************************************/
void Touch::init_touch (void)
{
T.st = C_INIT;
T.press = m_press;
T.unpress = m_unpress;
T.s[0].cap = 0;
T.s[1].cap = 0;
T.s[2].cap = 0;
T.s[0].config =
config_s0; // соответствие пинов и
программ обработки
T.s[1].config =
config_s2;
T.s[2].config =
config_s1;
T.s[0].func =
touch_s0; // соответствие реальных пинов программным тач левый
T.s[1].func =
touch_s2; // тач правый
T.s[2].func =
touch_s1; // тач с краёв
T.s[0].calib =
SLIDER_CALIB_0; //
T.s[1].calib =
SLIDER_CALIB_1;
T.s[2].calib =
SLIDER_CALIB_2;
T.s_count = 0;
T.mode = T_WAIT;
// init io pins
GPIO_ConfigOutput (S1_PORT1,
S1_PIN1, PortHiSpeed, PortPushPull);
// всё управление пинами через RI регистры
GPIO_ConfigOutput (S2_PORT1, S2_PIN1, PortHiSpeed,
PortPushPull);
GPIO_ConfigOutput (S3_PORT1,
S3_PIN1, PortHiSpeed, PortPushPull);
GPIO_ConfigOutput (C1_PORT1,
C1_PIN1, PortHiSpeed, PortPushPull);
GPIO_ConfigInput (C2_PORT1,
C2_PIN1, PinFloating, PinIrqOff); // ошибочно подключённый пин
S1_PORT = 0;
S2_PORT = 0;
S3_PORT = 0;
C1_PORT = 0;
RI->IOCMR1 =
RI_IOCMR1_CH1M * 0 /*!< Channel 1 I/O control mode */
|
RI_IOCMR1_CH4M * 0 /*!< Channel 4 I/O control mode */
|
RI_IOCMR1_CH7M * 0 /*!< Channel 7 I/O control mode */
|
RI_IOCMR1_CH10M * 0 /*!< Channel 10
I/O control mode */
|
RI_IOCMR1_CH13M * 0 /*!< Channel 13
I/O control mode */
|
RI_IOCMR1_CH16M * 1 /*!< Channel 16
I/O control mode */
|
RI_IOCMR1_CH19M * 1 /*!< Channel 19
I/O control mode */
|
RI_IOCMR1_CH22M * 0; /*!< Channel 22 I/O control mode */;
RI->IOCMR2 =
RI_IOCMR2_CH2M * 0 /*!< Channel 2 I/O control mode */
| RI_IOCMR2_CH5M * 0
/*!< Channel 5 I/O control mode */
|
RI_IOCMR2_CH8M * 0 /*!< Channel 8 I/O control mode */
|
RI_IOCMR2_CH11M * 0 /*!< Channel 11
I/O control mode */
|
RI_IOCMR2_CH14M * 0 /*!< Channel 14
I/O control mode */
|
RI_IOCMR2_CH17M * 0 /*!< Channel 17
I/O control mode */
|
RI_IOCMR2_CH20M * 0 /*!< Channel 20
I/O control mode */
|
RI_IOCMR2_CH23M * 1; /*!< Channel 23 I/O control mode */
RI->IOCMR3 =
RI_IOCMR3_CH3M * 0 /*!< Channel 3 I/O control mode */
|
RI_IOCMR3_CH6M * 0 /*!< Channel 6 I/O control mode */
|
RI_IOCMR3_CH9M * 0 /*!< Channel 9 I/O control mode */
|
RI_IOCMR3_CH12M * 0 /*!< Channel 12
I/O control mode */
|
RI_IOCMR3_CH15M * 0 /*!< Channel 15
I/O control mode */
|
RI_IOCMR3_CH18M * 1 /*!< Channel 18
I/O control mode */
|
RI_IOCMR3_CH21M * 0 /*!< Channel 21
I/O control mode */
|
RI_IOCMR3_CH24M * 0; /*!< Channel 24 I/O control mode */
RI->IOSR1 =
RI_IOSR1_CH1E * 0 /*!< Channel 1 I/O switch control */
|
RI_IOSR1_CH4E * 0 /*!< Channel 4 I/O switch control */
|
RI_IOSR1_CH7E * 0 /*!< Channel 7 I/O switch control */
|
RI_IOSR1_CH10E * 0 /*!< Channel 10 I/O switch control */
|
RI_IOSR1_CH13E * 0 /*!< Channel 13 I/O switch control */
|
RI_IOSR1_CH16E * 1 /*!< Channel 16 I/O switch control */
|
RI_IOSR1_CH19E * 1 /*!< Channel 19 I/O switch control */
|
RI_IOSR1_CH22E * 0; /*!< Channel 22
I/O switch control */
RI->IOSR2 =
RI_IOSR2_CH2E * 0 /*!< Channel 2 I/O switch control */
|
RI_IOSR2_CH5E * 0 /*!< Channel 5 I/O switch control */
|
RI_IOSR2_CH8E * 0 /*!< Channel 8 I/O switch control */
|
RI_IOSR2_CH11E * 0 /*!< Channel 11 I/O switch control */
|
RI_IOSR2_CH14E * 0 /*!< Channel 14 I/O switch control */
|
RI_IOSR2_CH17E * 0 /*!< Channel 17 I/O switch control */
|
RI_IOSR2_CH20E * 0 /*!< Channel 20 I/O switch control */
|
RI_IOSR2_CH23E * 1; /*!< Channel 23
I/O switch control */
RI->IOSR3 =
RI_IOSR3_CH3E * 0 /*!< Channel 3 I/O switch control */
|
RI_IOSR3_CH6E * 0 /*!< Channel 6 I/O switch control */
|
RI_IOSR3_CH9E * 0 /*!< Channel 9 I/O switch control */
|
RI_IOSR3_CH12E * 0 /*!< Channel 12 I/O switch control */
|
RI_IOSR3_CH15E * 0 /*!< Channel 15 I/O switch control */
|
RI_IOSR3_CH18E * 1 /*!< Channel 18 I/O switch control */
|
RI_IOSR3_CH21E * 0 /*!< Channel 21 I/O switch control */
|
RI_IOSR3_CH24E * 0; /*!< Channel 24
I/O switch control */
RI->ASCR1
= RI_ASCR1_AS0 * 0
/*!< Analog switch AS0 control */
|
RI_ASCR1_AS1 * 0 /*!< Analog switch AS1 control */
|
RI_ASCR1_AS2 * 0 /*!< Analog switch AS2 control */
| RI_ASCR1_AS3 * 0
/*!< Analog switch AS3 control */
|
RI_ASCR1_AS4 * 0 /*!< Analog switch AS4 control */
|
RI_ASCR1_AS5 * 1 /*!< Analog switch AS5 control */
|
RI_ASCR1_AS6 * 1 /*!< Analog switch AS6 control */
|
RI_ASCR1_AS7 * 1; /*!< Analog switch
AS7 control */
RI->ASCR2
= RI_ASCR2_AS8 * 0
/*!< Analog switch AS8 control */
|
RI_ASCR2_AS9 * 0 /*!< Analog switch AS9 control */
|
RI_ASCR2_AS10 * 0 /*!< Analog switch
AS10 control */
|
RI_ASCR2_AS11 * 0 /*!< Analog switch
AS11 control */
|
RI_ASCR2_AS14 * 0; /*!< Analog switch AS14 control */
}
Собственно, программа измерения ёмкости
кнопок:
/******************************************************************************
* процедура измерения ёмкости кнопки.
* Вход: сообщение (M_BUTTON_START)
* Выход - функция оценки кнопки t_button ()
* Данные о
ёмкости в структуре C
******************************************************************************/
void Touch::touch_func (void)
{
static u16 touch_timer =
0;
static u16 disch_timer =
0;
switch (T.mode)
{
case T_CONFIG:
T.s[T.s_count].config ();
T.mode =
T_COUNT;
break;
case T_COUNT:
if
(!T.s[T.s_count].func ()) // перенос заряда
{
T.s[T.s_count].cap++; //
если не зарядились - продолжать измерение
}
else
{
T.s_count++; //
иначе - следующий сенсор
T.mode =
T_DISCHARGE; // и разрядить
накопительный конденсатор
timer_reset
(disch_timer);
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_O0 // группа 3 (PB1) = 0
| RI_IOGCR_IOM4_FIO;
}
break;
case T_DISCHARGE: // разрядка конденсатора
if (timer_end
(disch_timer, DISCHARGE_TIME))
{
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
| RI_IOGCR_IOM4_FIO;
if (T.s_count
<= 2)
{
T.mode =
T_CONFIG;
}
else // когда все сенсоры измерились
{
T.s_count =
0;
t_slider
(&T); // функция оценки кнопок
T.mode =
T_WAIT;
}
}
break;
case T_WAIT:
if
(timer_end (touch_timer, TOUCH_INTERVAL)) //
ждём интервал 30 мс
{
timer_reset
(touch_timer);
T.s[0].cap =
0;
T.s[1].cap =
0;
T.s[2].cap =
0;
T.mode =
T_CONFIG;
}
break;
default:
T.mode =
T_CONFIG;
break;
}
}
И служебные программы:
void config_s0 (void)
{
RI->IOSR1 =
RI_IOSR1_CH16E * 0 /*!< Channel 16 I/O switch control */
|
RI_IOSR1_CH19E * 0; /*!< Channel 19 I/O switch control */
RI->IOSR2 =
RI_IOSR2_CH23E * 1; /*!< Channel 23
I/O switch control */
}
/******************************************************************************
* процедура переноса заряда на накопительный конденсатор
* Вход - указатель на структуру данных кнопки
* Выход - уровень заряда конденсатора
******************************************************************************/
bool touch_s0 (void)
{
bool reg;
// фаза 1 -
накачка конденсатора сенсорной панели
RI->IOGCR =
RI_IOGCR_IOM1_FIO
| RI_IOGCR_IOM2_O1
|
RI_IOGCR_IOM3_FIO
|
RI_IOGCR_IOM4_FIO; // пин s0 в 1
delay_charge ();
// фаза 3 -
перенос заряда на накопительный конденсатор
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIC
| RI_IOGCR_IOM3_FIC
|
RI_IOGCR_IOM4_FIO; // пин s0 и конденсатор подключён
RI->ASCR1 =
RI_ASCR1_AS5 | RI_ASCR1_AS7; // замкнут ключ
delay_transfer ();
// фаза 4 -
проверим уровень напряжения на входе измерительной ножки
reg = RI->IOIR3
& RI_IOIR3_CH18I;
// фаза 5 - пауза,
все входы высокоомные
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
|
RI_IOGCR_IOM4_FIO;
RI->ASCR1 = 0;
return reg;
}
void config_s1 (void)
{
RI->IOSR1 =
RI_IOSR1_CH16E * 0 /*!< Channel 16 I/O switch control */
|
RI_IOSR1_CH19E * 1; /*!< Channel 19 I/O switch control */
RI->IOSR2 =
RI_IOSR2_CH23E * 0; /*!< Channel 23
I/O switch control */
}
/******************************************************************************
* процедура переноса заряда на накопительный конденсатор
* Вход: нет
* Выход - уровень заряда конденсатора
******************************************************************************/
bool touch_s1 (void)
{
bool reg;
// фаза 1 -
накачка конденсатора сенсорной панели
RI->IOGCR =
RI_IOGCR_IOM1_O1
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
|
RI_IOGCR_IOM4_FIO; // пин s1 в 1
delay_charge ();
// фаза 3 -
перенос заряда на накопительный конденсатор
RI->IOGCR =
RI_IOGCR_IOM1_FIC
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIC
|
RI_IOGCR_IOM4_FIO; // пин s1 и конденсатор подключён
RI->ASCR1 =
RI_ASCR1_AS5 | RI_ASCR1_AS6;
delay_transfer ();
// фаза 4 -
проверим уровень напряжения на входе измерительной ножки
reg = RI->IOIR3
& RI_IOIR3_CH18I;
// фаза 5 - пауза,
все входы высокоомные
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
|
RI_IOGCR_IOM4_FIO;
RI->ASCR1 = 0;
return reg;
}
void config_s2 (void)
{
RI->IOSR1 =
RI_IOSR1_CH16E * 1 /*!< Channel 16 I/O switch control */
|
RI_IOSR1_CH19E * 0; /*!< Channel 19 I/O switch control */
RI->IOSR2 =
RI_IOSR2_CH23E * 0; /*!< Channel 23
I/O switch control */
}
/******************************************************************************
* процедура переноса заряда на накопительный конденсатор
* Вход: нет
* Выход - уровень заряда конденсатора
******************************************************************************/
bool touch_s2 (void)
{
bool reg;
// фаза 1 -
накачка конденсатора сенсорной панели
RI->IOGCR =
RI_IOGCR_IOM1_O1
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
| RI_IOGCR_IOM4_FIO; // пин s3 в 1
delay_charge ();
// фаза 3 -
перенос заряда на накопительный конденсатор
RI->IOGCR =
RI_IOGCR_IOM1_FIC
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIC
|
RI_IOGCR_IOM4_FIO; // пин s3 и конденсатор подключён
delay_transfer ();
// фаза 4 -
проверим уровень напряжения на входе измерительной ножки
reg = RI->IOIR3
& RI_IOIR3_CH18I;
// фаза 5 - пауза,
все входы высокоомные
RI->IOGCR =
RI_IOGCR_IOM1_FIO
|
RI_IOGCR_IOM2_FIO
|
RI_IOGCR_IOM3_FIO
|
RI_IOGCR_IOM4_FIO;
return reg;
}
А эти задержки я честно спёр у ST:
/*******************************************************************************
* Delay in NOPs to apply during charging time.
*******************************************************************************/
inline void delay_charge (void)
{
#define DELAY_CHARGE 5
#if DELAY_CHARGE > 0
nop();
#endif
#if DELAY_CHARGE > 1
nop();
#endif
#if DELAY_CHARGE > 2
nop();
#endif
#if DELAY_CHARGE > 3
nop();
#endif
#if DELAY_CHARGE > 4
nop();
#endif
#if DELAY_CHARGE > 5
nop();
#endif
#if DELAY_CHARGE > 6
nop();
#endif
#if DELAY_CHARGE > 7
nop();
#endif
#if DELAY_CHARGE > 8
nop();
#endif
#if DELAY_CHARGE > 9
nop();
#endif
#if DELAY_CHARGE > 10
nop();
#endif
#if DELAY_CHARGE > 11
nop();
#endif
#if DELAY_CHARGE > 12
nop();
#endif
}
/*******************************************************************************
* Delay in NOPs to apply during transfer charge time.
*******************************************************************************/
inline void delay_transfer (void)
{
#define DELAY_TRANSFER 5
#if DELAY_TRANSFER > 0
nop();
#endif
#if DELAY_TRANSFER > 1
nop();
#endif
#if DELAY_TRANSFER > 2
nop();
#endif
#if DELAY_TRANSFER > 3
nop();
#endif
#if DELAY_TRANSFER > 4
nop();
#endif
#if DELAY_TRANSFER > 5
nop();
#endif
#if DELAY_TRANSFER > 6
nop();
#endif
#if DELAY_TRANSFER > 7
nop();
#endif
#if DELAY_TRANSFER > 8
nop();
#endif
#if DELAY_TRANSFER > 9
nop();
#endif
#if DELAY_TRANSFER > 10
nop();
#endif
#if DELAY_TRANSFER > 11
nop();
#endif
#if DELAY_TRANSFER > 12
nop();
#endif
}
Особенности национальной рыбалки
настройки:
Поскольку работа сенсорного датчика напрямую
зависит от его конструкции, то в каждом вновь разрабатываемом устройстве
потребуется его калибровка. Начинается она с выбора ёмкости накопительного
конденсатора. Количество циклов заряда не должно быть больше 255, чтобы не
переполнилась переменная счётчика циклов, и меньше 10-20, чтобы была хорошая
разрешающая способность. Просто запускаем программу в режиме отладки и смотрим
значение T.s[n].flt не притрагиваясь к датчикам. Желательна величина 50-150. Если больше –
уменьшаем ёмкость накопительного конденсатора, меньше – увеличиваем.
Затем нужно откалибровать чувствительность
каждого датчика. Для этого устанавливаем значение T.s[n].calib в максимальную
чувствительность (примерно 10), и не спеша касаемся пальцем всех датчиков с
максимальной площадью контакта. Калибровочные коэффициенты можно принять чуть
меньше получившихся значений в T.s[n].calib.
Вот пока и всё. Программу интерпретации
получившихся значений ёмкости можно взять из предыдущей публикации или написать
самому. Программу слайдера я ещё проверяю и оптимизирую.
Комментариев нет:
Отправить комментарий