Pull to refresh

Разработка сенсорной клавиатуры для своих устройств

DIY
В данной статье я опишу процесс разработки сенсорной клавиатуры, которую можно использовать в своих девайсах. Такую клавиатуру нетрудно собрать, т.к. в ней отсутствуют механические части, а отсутствие механической обратной связи компенсируется изяществом использования.

В процессе разработки одного проекта мне потребовалась удобная клавиатура на 8 кнопок. Я решил прибегнуть к известному подходу – реализовать емкостные сенсоры.
Физическую теорию я уже описал в своей статье про девайс-сувенир, которой чувствовал, когда его берут в руку (http://habrahabr.ru/blogs/DIY/111627/)
Принцип остается совершенно тем же самым, единственное отличие в реализации – используются не два вывода микроконтроллера а один.
Для начала, видео того, к чему мы будем стремиться:



Шаг 1: Схемотехника



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

image

Порядок опроса сенсора будет слегка отличаться. Изначально на пин PD0 подан лог. 0.
Таким образом, ток течет от источника питания через мегаомный резистор и втекает в пин. Если сенсор был заряжен, то ток с него также будет стекать в пин PD0.

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

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

Ниже представлена фотография законченной печатной платы. Плата заказная, но изготовить такую в домашних условиях также не составляет труда. При использовании макетных плат в роли сенсора может выступать кусочек фольгированного стеклотекстолита, приклеенный поверх платы.

image

Над каждым сенсором расположен SMD диод для индикации нажатия.
Управляется все это микроконтроллером ATMega88, заведенном на частоте 20 МГц.
Таким образом, выход с этой клавиатуры можно сделать любой, какой вам будет нужен и какой потянет мега. В моем случае был удобен SPI (я прошивал и тестил не отключая от программатора, да и в устройстве уже была задействована эта шина), но вы может использовать встроенный в мегу наравне с SPI UART, I2C, или воспользоваться программной реализацией USB от ObjDev. Да, собственно, можно и аппаратной, типа FTDI преобразователя USB->UART.

Итоговая схема (скриншот из Altium) представлена ниже.

image

Опять-таки, ничего сложного, ничего лишнего – мега, тактирующая ее цепь, пара сглаживающих питание кондеров, разъем для программирования/подключении, 8 диодов и 8 сенсоров.

Что интересно: расстояние, на котором сенсор может почувствовать руку зависит от разрядности таймера, его частоты, а также сопротивления, через которое подключен сенсор. Объясняется это просто – более быстрый таймер сможет засечь более мелкие интервалы времени, а при отсутствии гальванического контакта с сенсором время зарядки существенно снижается. 20 МГц меги и ее 16-разрядного таймера хватает на то чтобы уверенно обнаруживать прикосновение через слой пластика (плексигласа) около 1 мм.
Можно слегка разогнать мегу и немного увеличить сопротивление, но лучше этим не увлекаться – стабильность работы разогнанной меги не гарантируется, а слишком большое сопротивление может сравнять ток заряда с током утечки, что сделает сенсор вечно неактивным.
Как бы то ни было, нормального режима работы вполне хватит для прикрытия сенсоров тонким кусочком пластика. Идеальным бы был вариант с напылением токопроводящего покрытия на стекло, но у меня не было особой возможности поэкспериментировать в этом направлении.

Дальше нас ждет код.

Шаг 2: Код



В принципе все уже описано выше, но для некоторой ясности приведу код проекта.

#include <avr/interrupt.h>
#include <avr/io.h>

unsigned char KBD_STATUS=0x00, TMP_STATUS=0x00; //Текущий статус клавиатуры и переменная, куда запишем новый статус

unsigned short SensorTimes[8]={0,0,0,0,0,0,0,0}; //Времена откликов сенсоров
unsigned short SensorHI[8]={0,0,0,0,0,0,0,0},      //Для ускорения вычислений - заранее посчитанные 
			   SensorLO[8]={0,0,0,0,0,0,0,0};  //верхний и нижний пороги
void CheckSensors();
unsigned short SensToLED[8]={8,16,32,1,4,2,1,2};  //Доп. массив, т.к. диоды висят на рандомных ногах)
ISR(TIMER0_OVF_vect)
{
		CheckSensors(); //Проверяем сенсоры
		for(unsigned short i=0;i<8;i++)
		{
			if(KBD_STATUS&(1<<i)==0)         //Зажигаем диоды. Проект старый, сейчас я бы так не написал
				if(i==3||i==7)
					PORTB|=SensToLED[i];  //Но править уже не буду. Куча ифов из за того что диоды
				else
					PORTC|=SensToLED[i]; //висят на разных портах и рандомных пинах.
			else
				if(i==3||i==7)
					PORTB|=SensToLED[i];
				else
					PORTC&=~SensToLED[i];
		}
}

ISR(SPI_STC_vect)  //Это прерывание вызывается если нас опросили по SPI
{
   	SPDR=KBD_STATUS; //Отправим им текущий статус!
}

void InitSPIMode3()  //Настройка SPI
{
	DDRB= 0b00010011;  
	PORTB=0b00000011;

	SPCR= 0b11001100;
	SPSR=0x00;
}

void Calibrate()  //Калибруем все сенсоры по очереди
{
unsigned char i=1,k=0;
	while(i!=0)
	{
		TCNT1=0x0000;
		TCCR1B=0x01;
		DDRD = ~i;
		while((PIND&i)==0);
		TCCR1B=0x00;
		DDRD|=i;
		SensorTimes[k]=TCNT1;
		SensorHI[k]=SensorTimes[k]+70;
		SensorLO[k]=SensorTimes[k]+20;
		k++;
		i<<=1;
	}
}

void CheckSensors()  //Проверяем все сенсоры по очереди
{
unsigned char i=1,k=0;
	TMP_STATUS=KBD_STATUS;
	TCNT1=0x0000;
	TCCR1B=0x01;
	DDRD = 0b11111110;
	while(i!=0)
	{
		TCNT1=0x0000;
		TCCR1B=0x01;
		DDRD = ~i;
		while((PIND&i)==0);
		TCCR1B=0x00;
		DDRD|=i;
		if(TCNT1>SensorHI[k])   //Гистерезис, чтобы не дрыгалось при граничном значении емкости
			TMP_STATUS|=i;
		else if(TCNT1<=SensorLO[k])
			TMP_STATUS&=~i;
		k++;
		i<<=1;
	}
	KBD_STATUS=TMP_STATUS;
}
int main()
{
DDRD=0xFF;
PORTD=0x00;

PORTC=0xFF;
DDRC=0xFF;

TCCR0=0b00000101;
TCNT0=0x00;

TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;

MCUCR=0x00;
TIMSK=0x01;
ACSR=0x80;
SFIOR=0x00;
InitSPIMode3();

for(int i=0;i<1024;i++) //Просто чтобы не калиброваться сразу как подадут питание
	CheckSensors();  // подрыгаем сенсорами)
KBD_STATUS=0x00;
TMP_STATUS=0x00;
Calibrate();                  //Калибруемся
sei();
while(1);
}
Tags:
Hubs:
Total votes 103: ↑101 and ↓2 +99
Views 27K
Comments Comments 50