Сенсорная кнопка на микроконтроллере ATtiny

12/08/2013 - 19:34 Павел Бобков

Введение

   Во многих современных устройствах в качестве органов управления используются сенсорные кнопки. Например, у меня на кухне с помощью сенсорных кнопок включается и выключается варочная поверхность. 
   Сенсорные кнопки хороши тем, что для них не нужны отверстия в корпусе, и у них отсутствует механический износ, присущий традиционным кнопками. Кроме того конструкция сенсорной кнопки, если не считать электронную начинку, намного проще механической, ведь это обычная проводящая поверхность изолированная слоем диэлектрика, и ей можно придать практически любую конфигурацию. 
   
В этой статье я расскажу о простой реализации сенсорной кнопки на микроконтроллере. 

Принцип работы сенсорной кнопки

   По сути, сенсорная кнопка - это сенсор, то есть какая-то проводящая поверхность, и контроллер, измеряющий его емкость (способность накапливать электрический заряд). При неизменных условиях внешней среды емкость сенсора не меняется. Но когда к нему подносится палец, его емкость увеличивается. По этому изменению контроллер "делает вывод", что пользователь нажал на кнопку. 
   Емкость измеряется путем подсчета времени, которое требуется для ее заряда до определенного уровня напряжения. Это время зависит как от значения емкости, так и от величины зарядного тока, которая задается какой-то внешней цепью. В простейшем случае эту роль может выполнять резистор, подключенный одним концом к источнику питания, а другим к сенсору. 


   где Cs - емкость сенсора, Cf - емкости вносимые прикосновением пальца. 

   Поскольку емкость сенсора и ее изменения очень маленькие (единицы, десятки пикофарад), то время заряда тоже будет небольшим. Чтобы упростить измерение времени заряда, нужно увеличить его значение, а для этого необходимо заряжать емкость сенсора очень маленьким током (микроамперами).

Конфигурация сенсорной кнопки

   От конфигурации (от его формы и размера) сенсора зависит его емкость. У меня в проекте использовался сенсор в виде вытравленной круглой контактной площадки диаметром 1 см. Также я пробовал использовать прямоугольные кусочки фольгированного текстолита разного размера. Чем больше площадь сенсора, тем больше его емкость и тем более чувствительную сенсорную кнопку можно сделать. 
   Чувствительную в том плане, что она будет в состоянии срабатывать через толстый слой диэлектрика. Вообще на эту тему лучше почитать атмеловские материалы, поскольку сейчас я об этом мало что могу сказать. 

Схема

   

   Схема, которую использовал я, представлена на рисунке выше. 
   Сенсор через резистор подключен к выводу микроконтроллера, который подтянут высокоомным резистором к плюсу питания. Первый резистор служит простейшей защитой от электростатики (ESD), а второй ограничивает зарядный ток. 
   В начальный момент вывод микроконтроллера работает в режиме выхода и на нем установлен уровень логического нуля. Емкость сенсора, если она имеет какой-то заряд, быстро разряжается на внутренние цепи микроконтроллера. Когда она разрядится вывод микроконтроллера переключится в режим входа с отключенным подтягивающим резистором и емкость сенсора начинает заряжаться через внешний высокоомный резистор. В это время программа опрашивает состояние вывода микроконтроллера и подсчитывает количество циклов опроса. Когда емкость зарядится до напряжения логической единицы, микроконтроллер зафиксирует это и сравнит состояние счетчика с каким-то пороговым значением. По результату этого сравнения микроконтроллер определяет поднесен ли к сенсору палец или нет. 

Код

   Сам принцип думаю понятен, теперь можно посмотреть как это может выглядеть в Си коде. Для реализации одиночных сенсорных кнопок, я написал небольшой программный модуль - драйвер. 

   Чтобы добавить его в свой проект, нужно переписать заголовочный и сишный файл драйвера (sensor.c и sensor.h) в папку проекта, добавить сишный файл sensor.c к проекту внутри среды разработки и включить заголовочный файл драйвера sensor.h (с помощью директивы include) в свой файл, где будут использоваться функции драйвера. 

   Разберем как им пользоваться. Для хранения настроек сенсора используется структура данных sensor_t. В настройки сенсорной кнопки входит адрес порта, номер вывода в порте, пороговое значение и текущее состояние. Чтобы добавить к проекту сенсорную кнопку, нужно для начала создать переменную типа sensor_t. 

Пример:


#include "sensor.h"
....
//объявляем сенсорную кнопку
sensor_t sensor1;


   Для инициализации используется функция SENS_Init(...) которой нужно передать адрес переменной, хранящей настройки, номер вывода микроконтроллера, адрес используемого порта и пороговое значение срабатывания. Пороговое значение зависит от емкости сенсора (от его геометрии) и от зарядного тока. Сейчас значение можно определить только экспериментально, потом я хочу добавить функцию калибровки.

Пример:


//инициализация sensor1 - 0-й вывод порта B, порог - 25 циклов опроса.
SENS_Init(&sensor1, 0, &PORTB, 25);


   Опрос одной сенсорной кнопки выполняет функция SENS_Poll(...), которой нужно передать адрес переменной типа sensor_t. Эта функция возвращает состояние сенсорной кнопки. Для всех возможных состояний сенсорной кнопки объявлен специальный тип данных sensor_state_t. Но функция опроса возвращает только два состояния SENS_PRESSED и SENS_NOT_PRESSED. Остальные состояния сделаны на будущее.

Пример:



//опрос одной сенсорной кнопки
if (SENS_Poll(&sensor1) == SENS_PRESSED){
// зажечь светодиод
...
}

   Функция SENS_Poll(...) выполняет один цикл разряда- заряда сенсора.


void SENS_Poll(sensor_t *sens)
{
   uint8_t count = 0;
   uint8_t pin = sens->pin;
   volatile uint8_t *port = sens->port;

   /*разрядка конденсатора*/
   DirReg(port) |= 1<<pin;
   while(PinReg(port)&(1<<pin) != 0 ){
      count++;
      if (count > SENS_COUNT_MAX){
         return;
      }
   }

   /*заряд конденсатора и подсчет времени*/
   count = 0;
   DirReg(port) &= ~(1<<pin);
   while((PinReg(port)&(1<<pin)) == 0){
      count++;
      if (count > SENS_COUNT_MAX){
         break;
      }
   }

   /*сравнение вр. разряда с калиброванным значением*/
   if (count > sens->calibr){
      sens->state = SENS_PRESSED;
   }
   else{
      sens->state = SENS_NOT_PRESSED;
   }
}


  Для достоверного определения состояния сенсора, эту функцию лучше вызывать несколько раз. Так же как делается при опросе механических кнопок. Работу функции нежелательно прерывать прерываниями, иначе подсчет времени заряда будет некорректным. Частоту вызова функции опроса можно сделать такой же, как при работе с механическими кнопками. Функция не оптимальная, из-за того, что доступ к порту осуществляется через указатель. Но это позволяет упростить процедуру настройки сенсора, используя только адрес регистра PORTX, а адреса регистров DDRX и PINX вычисляются из него.  
   И последняя функция драйвера - это SENS_GetState(...). Она просто возвращает текущее значение сенсорной кнопки.

Пример:


//переменная типа sensor_state_t
sensor_state_t state;

...
state = SENS_GetState(&sensor1);
if (state == SENSOR_PRESSED){
//сделать что-нибудь
...
}


   Код самого тестового проекта очень простой. Инициализация выхода для светодиода, инициализация сенсорной кнопки и бесконечный опрос кнопки в цикле while(1). Никаких прерываний не используется. Частота работы микроконтроллера 9.6 МГц.

Файлы

Сенсорная кнопка на attiny13. Проект для IAR AVR.

Ссылки

Сенсорные кнопки. Реализация Петрова.

Комментарии   

# Владимир 18.08.2013 20:41
Код:
/*разрядка конденсатора*/
DirReg(port) |= 1<<pin;
while(PinReg(port)&(1<<pin) != 0 ){
count++;
if (count > SENS_COUNT_MAX)
return;
}
}


Не понятен этот момент:
- переводим вывод в низкое состояние и ждем разряда емкости;
- в течении максимально допустимого времени лог. состояние пина так и не сменилось (аварийная ситуация! (например, КЗ));
- плюем на все, оставляем ногу гореть и выходим?

Или я неправильно понял от чего защищает эта проверка?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 18.08.2013 21:13
От зависания в цикле.
Ответить | Ответить с цитатой | Цитировать
# Владимир 19.08.2013 06:26
да это очевидно, что от зависания.
но каковы могут быть причины этого зависания?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.08.2013 11:10
Например такая. Вошли в цикл, а в это время вызвалось прерывание, в котором вывод микроконтроллер а был переведен в единичное состояние.
В принципе этот цикл можно убрать и заменить маленькой программной задержкой.
Ответить | Ответить с цитатой | Цитировать
# Владимир 19.08.2013 11:15
Но ведь...
Ладно, понятно. :-)
Ответить | Ответить с цитатой | Цитировать
# САБ 19.08.2013 12:41
При изменении внешних условий (влажность) время заряда будет изменяться. Поэтому мне кажется хорошо бы пороги подстраивать по результатам предыдущих измерений. Или отказаться от порогов и измерять первую производную (разницу между текущим и предыдущим значением) и уже по ее величине и знаку делать вывод о нажатии/отпуска нии.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.08.2013 15:03
Еще не придумал, как лучше сделать калибровку.
Ответить | Ответить с цитатой | Цитировать
# Wimpexs 25.11.2013 14:01
Скажите, а ваш контроллер планирует быть чуть более мадернезировней ? Или можно немного изменить схему, изменив ее на клавишу?
Понимаю что рассматривается конкретный вариант сенсора и конкретно одного порта.
Ответить | Ответить с цитатой | Цитировать
# Владимир 25.11.2013 14:17
Лучше скажите конкретно какую задачу Вам требуется решить?
Ответить | Ответить с цитатой | Цитировать
# Хруст 17.05.2015 22:03
Сделал все работает) Но Нужно несколько кнопок (12 шт)
Код:#include <avr/io.h>
#include "sensor.h"
#include <util/delay.h>
#define F_CPU 8000000

volatile uint8_t tim_flag = 0;
uint8_t count1 = 0;
sensor_t sensor1;
sensor_t sensor2;
int main(void)
{
DDRB = (1<<3);
PORTB = (0<<3);

SENS_Init(&sensor1, 0, &PORTB, 40);
SENS_Init(&sensor2, 4, &PORTB, 40);

while(1){

SENS_Poll(&sensor1);

if (SENS_GetState(&sensor1) == SENS_PRESSED) {
PORTB |= (1 << PB3); // зажигаем светодиод
_delay_ms(200); // задержка 200мс
PORTB &= ~(1 << PB3); // гасим светодиод
}
else {
PORTB &= ~(1 << PB3); // гасим светодиод
}

SENS_Poll(&sensor2);

if (SENS_GetState(&sensor2) == SENS_PRESSED) {
PORTB |= (1 << PB3); // зажигаем светодиод
_delay_ms(200); // задержка 200мс
PORTB &= ~(1 << PB3); // гасим светодиод
}
else {
PORTB &= ~(1 << PB3); // гасим светодиод
}

}
return 0;
}

Пробовал написать так, не работает( Помогите пожалуйста
Ответить | Ответить с цитатой | Цитировать
# Ferit 23.12.2015 06:55
Приветствую. Посмотрел этот проект сенсорной кнопки, но остановило меня наличие микроконтроллер а, т.к. микроконтроллер есть в виде Arduino. И собрал свою кнопку на транзисторах. К сожалению не могу вставить картинку схемы, она есть тут - http://ampexpert.ru/sensornaya-knopka/
Ответить | Ответить с цитатой | Цитировать
# AGE 24.12.2015 15:02
Ваша кнопка работает в основном от наводок осветительной сети на тело человека, в пустыне и лесу ваша схема, скорее всего, работать не будет. Предложенная схема работает на другом принципе, теоретически будет работать всегда независимо от апокалипсиса. Кроме того на одной ATtiny13 можно сделать три кнопки и разную логику - повторитель или триггер, а так-же очистку от дребезга, автоповтор нажатия, итд. итп.
Ответить | Ответить с цитатой | Цитировать
# zur 02.02.2016 08:08
фьюзы конфигурации где можно наидти?
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий

Защитный код
Обновить