Сенсорные кнопки
02/02/2011 - 20:32
Pashgan, Petrov
Управление с помощью компьютера исполнительным механизмом или иным устройством не всегда удобно. Первые два дня это, конечно, интересно, но потом начинает напрягать, потому что приходится совершать много лишних действий – включать компьютер, запускать программу и т.д. Возникает вопрос, а не изготовить ли для этих целей автономный управляющий модуль с клавиатурой и дисплеем?
Так как делается это для себя, то требуется дешевое, но в то же время красивое решение. С выбором дисплея особо не помудришь, дешевле и удобнее символьных LCD с контроллером еще не придумали, а вот при выборе клавиатуры можно хорошенько сэкономить.
Рассмотрим варианты. За готовую матричную клавиатуру придется выложить 100-200 рублей. Клавиатура из кнопок обойдется рублей в 36 (3р * 12 кнопок), не считая колпачков. Сенсорную клавиатуру можно изготовить почти задаром из куска фольгированного текстолита, при этом в корпусе устройства не придется сверлить отверстия под кнопки. По-моему выбор очевиден, осталось только понять, как с такой клавиатурой работать. Но для начала нужно хотя бы разобраться с одиночной сенсорной кнопкой.
Одиночная сенсорная кнопка представляет собой металлическую площадку, подключенную к выводу микроконтроллера. Поскольку любое проводящее тело обладает емкостью, величина которой зависит от геометрии тела и окружающего его диэлектрика, такое подключение равносильно подключению к выводу конденсатора.
Человеческое тело тоже обладает емкостью (~100..300 pF), поэтому при поднесении пальца к сенсору суммарная емкость на входе микроконтроллера увеличится (ведь при параллельном соединении конденсаторов их емкости складываются). По именениям этой емкости и можно делать вывод о прикосновении к сенсору.
С1 — паразитная емкость вывода мк, С2 — емкость сенсора, С3 и С4 – емкости, вносимые прикосновением.
Поскольку все КМОП микросхемы болезненно переносят воздействие статического электричества, а человеческое тело ,обладая вышеуказанной емкостью, может заряжаться до нескольких тысяч вольт, схему следует дополнить небольшой защитной цепью.
При прикосновении к сенсору, накопленный телом заряд благополучно разрядится через один из диодов. В принципе такая защитная схема (не считая резистора) спрятана внутри микроконтроллера, но полагаться на нее не стоит, так как встроенные диоды выдерживают ток лишь в 1 ма.
Кстати, внешние диоды добавят к входной емкости еще пару тройку пикофарад.
Ну ладно, принцип понятен, а каким образом измерить емкость? Величину емкости можно оценить по времени ее заряда. Подключим к выводу микроконтроллера высокоомный подтягивающий резистор (смотри схему) и получим RC цепь. Манипулируя выводом микроконтроллера можно вызвать заряд и разряд нашего виртуального конденсатора, а контролирую потенциал вывода в процессе заряда емкости - оценивать ее величину. Чем больше емкость, тем медленнее она будет заряжаться.
Вырисовывется следующий алгоритм работы:
1. Переключаем вывод микроконтроллера в режим выхода и сбрасываем в ноль. Паразитная емкость разрядится на внутренние цепи микроконтроллера.
2. Переключаем вывод микроконтроллера в режим входа в состоянии Hi-Z. Емкость начнет заряжаться через внешний подтягивающий резистор.
3. Контролируем состояние вывода и отсчитываем время. Как только емкость зарядится до напряжения логической единицы, снимаем показания счетчика.
4. Сравниваем полученное значение счетчика с заданной эталонной величиной и определяем, увеличилась ли емкость или нет.
Ничего сложного и можно перейти к делу. Представляю сделанную на скорую руку, но, тем не менее, рабочую схему и исходники устройства.
Исходник не большой и, я думаю, main.c можно выложить полностью, он у меня хорошо прокомментирован.
//***************************************************************************
//
// Author(s)...: Pashgan http://ChipEnable.Ru, Petrov http://svyaz.blogspot.com/
//
// Target(s)...: ATtiny2313
//
// Compiler....: IAR EWA 5.11
//
// Description.: ATtiny sensor
//
// Data........: 19.01.11
//
//***************************************************************************
#include <ioavr.h>
#include <intrinsics.h>
#include "usart.h"
//макросы для работы с битами
#define InvBit(reg, bit) reg ^= (1<<(bit))
#define ClearBit(reg, bit) reg &= (~(1<<(bit)))
#define SetBit(reg, bit) reg |= (1<<(bit))
#define BitIsSet(reg, bit) ((reg & (1<<bit)) != 0)
//сколько циклов опроса кнопка должна удерживаться
#define THRESHOLD 20
// величина которую, возможно придется подстраивать
#define TOUCH_COUNT 1
//переменные для подсчета количества переполнений Т0
volatile unsigned char sub_time_pin0 = 0;
volatile unsigned char sub_time_pin1 = 0;
//переменная для защиты от дребезга
unsigned char comp = 0;
int main( void )
{
USART_Init();
// Инициализация таймера T0 для Tiny2313
TIMSK = (1<<OCIE0A); //разрешаем прерывание таймера T0 при событии совпадение
TCCR0A = (1<<WGM01)|(0<<WGM00);
TCCR0B = (0<<CS02)|(1<<CS01)|(0<<CS00); //режим СТС, прескалер - 8
TCNT0 = 0; //обнуляем счетный регистр
OCR0A = 0x32; //прерывания каждые ~ 50мкс
// Инициализация таймера T1 для Tiny2313
TIMSK |= (1<<OCIE1A); //разрешаем прерывание таймера т1 при событии совпадение
TCCR1A = (0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(0<<CS10); //режим работы СТС, прескалер - 8
TCNT1 = 0; //обнуляем счетный регистр
OCR1A = 0x2710; //прерывания каждые 10мс
//разрешаем прерывания
__enable_interrupt();
USART_SendStr("Button: ");
DDRD |=(1<<PD2)|(1<<PD3)|(1<<PD4); //устанавливаем PD2, PD3 и PD4 на выход
PORTD = 0;
//++++++++++++++++++++++++++++++++++++++++++++++++++
while(1){
/*если на выводе PB0 единица и таймер переполнился
несколько раз -> увеличить антидребезговую переменную
затем, если значение переменной comp превысило порог ->
инвертировать пин PD2, послать по УАРТУ номер кнопки,
обнулить антидребезговую переменную */
if (BitIsSet(PINB, PB0)) {
if (sub_time_pin0 > TOUCH_COUNT) {
comp++;
if (comp >= THRESHOLD){
InvBit(PORTD,PD2);
USART_SendStr("1 ");
comp = 0;
}
}
sub_time_pin0 = 0;
}
/* то же самое */
if (BitIsSet(PINB, PB1)) {
if (sub_time_pin1 > TOUCH_COUNT) {
comp++;
if (comp >= THRESHOLD){
InvBit(PORTD, PD3);
USART_SendStr("2 ");
comp=0;
}
}
sub_time_pin1 = 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++
}
return 0;
}
// Прерывание Таймера 0 используемого для сканирования пина
#pragma vector = TIMER0_COMPA_vect
__interrupt void Timer0Comp(void)
{
// Увеличиваем на 1 счетчики переполнений таймера
sub_time_pin0++;
sub_time_pin1++;
//это для мониторинга в Протеусе
InvBit(PORTD,PD4);
}
// Прерывание Т1
//Инициализация порта, к которому подключены сенсорные кнопки согласно алгоритму
#pragma vector = TIMER1_COMPA_vect
__interrupt void Timer1CompA(void)
{
//переключаем порт в режим выхода и устанавливаем 0
//разряжая паразитную емкость на внутренние цепи AVR
DDRB = 0xFF;
PORTB = 0x00;
//переключаем порт в режим входа в состоянии Hi-Z
DDRB = 0x00;
//обнуляем количество срабатываний для нулевого и первого выводов PORTB
sub_time_pin0 = 0;
sub_time_pin1 = 0;
TCNT0 = 0; // Обнуляем счетный регистр T0
Tagged under
Comments
2. Входы защищены двумя диодами.
3. Можно добавить между выводом и подтягивающим резистором низкоомный резистор для ограничения входного тока.
4. И наконец кто мешает поставить стабилитроны или супрессоры (TVS-диоды).
Дык кто вам мешает, окажите услугу покажите как надо! Мы вам спасибо скажем. А критиковать то проще всего, не иначе. 8)
Предрекая все дальнейшие размышления и вопросы предлагаю ознакомится с AppNote от Atmel
(http://www.atmel.com/dyn/resources/prod_documents/doc10620.pdf)
Документ многим будет интересен, как в части защиты от статики, так и в части юзабилити.
наводки окружающих электрических полей,которые вокруг нас наведённые на тело человека 50 гц,передаёт при прикосновении к сенсору.а никакое не изменение ёмкости.
Представьте,что ваше устройство надо будет эксплуатировать в чистом поле, где нет ЛЭП и прочих коммуникаций и электрических полей,
Ваше устройство там работать не будет,
нечему там наводиться на тело человека.
Ну чего нам ссылаться на чужие источники,мало ли кто какой бред пишет?..)))и проверки этих сомнительных теорий,
Хотя,любая теория проверяется практикой,
поэтому,
давайте рассуждать логически и здраво,
возьмите в руку щуп осциллографа,и Вы сразу увидите эти наводки на экране в виде синусоиды 50 гц,
коснитесь пальцем НЧ усилителя,и в колонках Вы услышите этот самый фон 50 Гц,
наводимый на ваше тело, Ваш палец и передаёт на вход УНЧ этот самый сигнал, точно так же,как и в случае с сенсорами,
если только речь не идёт об действительно измерении ёмкости вашего тела в пикофарадах и сравнением этой ёмкости после измерения её с заданной величиной,
но в этой статье,как я понял ,речь идёт вовсе не о таком варианте работы схемы.
Я собственно об этом куске текста в Вашей статье.
Еще раз цитирую вами же процитированный кусок Quote: . Нет прямого контакта! по ТЗ, его нет :D По нахватались вершков. В коментах даже на апноуты от атмела ссылки есть, потрудитесь почитать. Quote: Именно об этом идет речь. :P
Я оказался не прав,вынужден признать,
невнимательно читал,сейчас перечитал заново статью.
получается нечто похожее на QT102 от Атмел.
http://s002.radikal.ru/i197/1112/82/60e7235f654c.jpg
Впаял все компоненты, прошил, но не работатет.
При подаче питани постоянно горит красная один из светодиодов, второй не горит вообще. нет никакой реакции на прикосновение к сенсору.
Помогите разобраться.
Я пока не разобрался для чего у МК на ноге PD4 прямоугольные импульсы подаються на вход осцилографа, но если ее отключить - то реакция на нажатия кнопки довольно быстрая. Т.е. с осцилографом нужно подождать больше времни на то чтобы сработало нажатие.
Но вроде напутано в коде с PD2 PD3 PD4.
Попытался добавить звук, но включение еще одного таймера вызвало периодическое мигание светодиодов. Пока не разобрался где влияние. Прошу помочь, кто знает.
Code:
#include "stm8l10x_tim2.h"
#include "stm8l10x_tim3.h"
#include "stm8l10x_tim4.h"
#define Threshold 20
#define TouchCount 6 // подбор нужен
volatile unsigned char stpin0 = 0;
volatile unsigned char stpin1 = 0;
unsigned char comp = 0;
int main( void )
{
GPIO_Init(GPIOC,GPIO_Pin_0,GPIO_Mode_Out_PP_High_Fast);
GPIO_Init(GPIOC,GPIO_Pin_1,GPIO_Mode_Out_PP_High_Fast);
GPIO_Init(GPIOD,GPIO_Pin_0,GPIO_Mode_Out_PP_High_Fast);
CLK_MasterPrescalerConfig(CLK_MasterPrescaler_HSIDiv1);
// TIM2 прерывания каждые 50 мкс
CLK_PeripheralClockConfig(CLK_Peripheral_TIM2,ENABLE);
TIM2_TimeBaseInit(TIM2_Prescaler_1,TIM2_CounterMode_Up,200);
TIM2_ITConfig(TIM2_IT_Update,ENABLE); TIM2_Cmd(ENABLE);
// TIM3 прерывания каждые 10 мс
CLK_PeripheralClockConfig(CLK_Peripheral_TIM3, ENABLE);
TIM3_TimeBaseInit(TIM3_Prescaler_2, TIM3_CounterMode_Up, 40000);
TIM3_ITConfig(TIM3_IT_Update,ENABLE); TIM3_Cmd(ENABLE);
// TIM4 Звук
CLK_PeripheralClockConfig(CLK_Peripheral_TIM4,ENABLE);
TIM4_TimeBaseInit(TIM4_Prescaler_256, 200);
TIM4_ITConfig(TIM4_IT_Update,ENABLE);
TIM4_Cmd(ENABLE);
__enable_interrupt();
while(1)
{
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)){if(stpin0>TouchCount){comp++;if(comp>=Threshold){GPIO_ToggleBits(GPIOC,GPIO_Pin_0);comp=0;}}stpin0=0;}
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)){if(stpin1>TouchCount){comp++;if(comp>=Threshold){GPIO_ToggleBits(GPIOC,GPIO_Pin_1);comp=0;}}stpin1=0;}
}
return 0;
}
INTERRUPT_HANDLER(TIM2_OVF, 19)
{
if (TIM2_GetFlagStatus(TIM2_FLAG_Update))
{
stpin0++; stpin1++;
TIM2_ClearFlag(TIM2_FLAG_Update);
}
}
INTERRUPT_HANDLER(TIM3_OVF, 21)
{
if (TIM3_GetFlagStatus(TIM3_FLAG_Update))
{
GPIO_Init(GPIOB, GPIO_Pin_0, GPIO_Mode_Out_OD_Low_Fast);
GPIO_Init(GPIOB, GPIO_Pin_1, GPIO_Mode_Out_OD_Low_Fast);
GPIO_Init(GPIOB, GPIO_Pin_0, GPIO_Mode_In_FL_No_IT);
GPIO_Init(GPIOB, GPIO_Pin_1, GPIO_Mode_In_FL_No_IT);
stpin0 = 0; stpin1 = 0;
// ? Обнуляем счетный регистр
TIM3_ClearFlag(TIM3_FLAG_Update);
}
}
INTERRUPT_HANDLER(TIM4_OVF, 25)
{
if (TIM4_GetFlagStatus(TIM4_FLAG_Update))
{
GPIO_ToggleBits(GPIOD,GPIO_Pin_0); // Вывод звука
TIM4_ClearFlag(TIM4_FLAG_Update);
}
}
Code:
#define TouchCount 6
int time=6500;
void Sound(int Freq);
char ovf0,ovf1,ovf2,ovf3,ovf4,ovf5,ovf6,ovf7;
int main( void )
{
GPIO_Init(GPIOA,GPIO_Pin_2,GPIO_Mode_Out_OD_HiZ_Slow);
CLK_MasterPrescalerConfig(CLK_MasterPrescaler_HSIDiv1);
// TIM4 прер. 40 мкс
CLK_PeripheralClockConfig(CLK_Peripheral_TIM4,ENABLE);
TIM4_TimeBaseInit(TIM4_Prescaler_4, 155);
TIM4_ITConfig(TIM4_IT_Update,ENABLE); TIM4_Cmd(ENABLE);
// TIM2 прер. 2.5 мс
CLK_PeripheralClockConfig(CLK_Peripheral_TIM2, ENABLE);
TIM2_TimeBaseInit(TIM2_Prescaler_1, TIM2_CounterMode_Up, 40000);
TIM2_ITConfig(TIM2_IT_Update,ENABLE); TIM2_Cmd(ENABLE);
//----- Инициализация TIM3 -- Звук
CLK_PeripheralClockConfig(CLK_Peripheral_TIM3, ENABLE);
TIM3_TimeBaseInit(TIM3_Prescaler_2, TIM3_CounterMode_Up, 20000);
TIM3_ITConfig(TIM3_IT_Update,ENABLE); TIM3_Cmd(DISABLE);
__enable_interrupt();
while(1)
{
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)) {if(ovf0 > TouchCount) Sound(4000); ovf0=0;}
. . .
// пока проверил на 11 кнопках
if (time==0)
{
TIM3_Cmd(DISABLE); time=6500;
GPIO_Init(GPIOA,GPIO_Pin_2,GPIO_Mode_Out_OD_HiZ_Slow);
} else time--;
}
return 0;
}
INTERRUPT_HANDLER(TIM4_UPD, 25)
{
ovf0++; ovf1++; ovf2++; ovf3++; ovf4++; ovf5++; ovf6++; ovf7++;
TIM4_ClearFlag(TIM4_FLAG_Update);
}
INTERRUPT_HANDLER(TIM2_UPD, 19)
{
GPIO_Init(GPIOB, GPIO_Pin_All, GPIO_Mode_Out_OD_Low_Fast);
GPIO_Init(GPIOB, GPIO_Pin_All, GPIO_Mode_In_FL_No_IT);
TIM2_ClearFlag(TIM2_FLAG_Update);
}
INTERRUPT_HANDLER(TIM3_UPD, 21) // Звук
{
GPIO_ToggleBits(GPIOA,GPIO_Pin_2);
TIM3_ClearFlag(TIM3_FLAG_Update);
}
void Sound(int Freq)
{
TIM3_TimeBaseInit(TIM3_Prescaler_2, TIM3_CounterMode_Up, Freq); TIM3_Cmd(ENABLE);
}
RSS feed for comments to this post