В данной статье я опишу процесс разработки сенсорной клавиатуры, которую можно использовать в своих девайсах. Такую клавиатуру нетрудно собрать, т.к. в ней отсутствуют механические части, а отсутствие механической обратной связи компенсируется изяществом использования.
В процессе разработки одного проекта мне потребовалась удобная клавиатура на 8 кнопок. Я решил прибегнуть к известному подходу – реализовать емкостные сенсоры.
Физическую теорию я уже описал в своей статье про девайс-сувенир, которой чувствовал, когда его берут в руку (http://habrahabr.ru/blogs/DIY/111627/)
Принцип остается совершенно тем же самым, единственное отличие в реализации – используются не два вывода микроконтроллера а один.
Для начала, видео того, к чему мы будем стремиться:
Схемотехника сенсоров слегка изменилась, в связи с тем, что необходимо использовать только одну ногу микроконтроллера на сенсор, а не две. Впрочем, если вам не жалко лишних ног, то можно все оставить по-старому.
![image](https://habrastorage.org/r/w1560/storage/habraeffect/d3/31/d3315275825833172f0420fb5e242ae9.png)
Порядок опроса сенсора будет слегка отличаться. Изначально на пин PD0 подан лог. 0.
Таким образом, ток течет от источника питания через мегаомный резистор и втекает в пин. Если сенсор был заряжен, то ток с него также будет стекать в пин PD0.
В момент опроса мы переключаем пин с выхода на вход (подтяжки отключены!). В этот момент, пин переходит в высокоимпедансное состояние, с сопротивлением порядка нескольких десятков(а то и сотен) МОМ. Ток в направлении пина практически прекращает течь, и начинает течь в сторону сенсора. Как только сенсор зарядится до напряжения свыше уровня лог. 1, данный вход микроконтроллера покажет единицу.
Измерив время, которое прошло с момента перевода PD0 в высокоимпедансное состояние до появления на нем лог 1, можно сделать вывод об изменении емкости сенсора, а значит, отловить момент прикосновения.
Конструктивно сенсор представляет собой прямоугольную контактную площадку 10х10 мм, но на самом деле форма его практически ничем не ограничивается, можно делать круги, змейки, делить их на сегменты и сектора — в общем ввод информации в любом виде.
Ниже представлена фотография законченной печатной платы. Плата заказная, но изготовить такую в домашних условиях также не составляет труда. При использовании макетных плат в роли сенсора может выступать кусочек фольгированного стеклотекстолита, приклеенный поверх платы.
![image](https://habrastorage.org/r/w780q1/storage/habraeffect/fb/35/fb3532fa5a1945014ba05a33f9dd09ad.jpg)
Над каждым сенсором расположен SMD диод для индикации нажатия.
Управляется все это микроконтроллером ATMega88, заведенном на частоте 20 МГц.
Таким образом, выход с этой клавиатуры можно сделать любой, какой вам будет нужен и какой потянет мега. В моем случае был удобен SPI (я прошивал и тестил не отключая от программатора, да и в устройстве уже была задействована эта шина), но вы может использовать встроенный в мегу наравне с SPI UART, I2C, или воспользоваться программной реализацией USB от ObjDev. Да, собственно, можно и аппаратной, типа FTDI преобразователя USB->UART.
Итоговая схема (скриншот из Altium) представлена ниже.
![image](https://habrastorage.org/r/w1560/storage/habraeffect/1e/0e/1e0e0d4b59bd52729372be7cb1a80a27.png)
Опять-таки, ничего сложного, ничего лишнего – мега, тактирующая ее цепь, пара сглаживающих питание кондеров, разъем для программирования/подключении, 8 диодов и 8 сенсоров.
Что интересно: расстояние, на котором сенсор может почувствовать руку зависит от разрядности таймера, его частоты, а также сопротивления, через которое подключен сенсор. Объясняется это просто – более быстрый таймер сможет засечь более мелкие интервалы времени, а при отсутствии гальванического контакта с сенсором время зарядки существенно снижается. 20 МГц меги и ее 16-разрядного таймера хватает на то чтобы уверенно обнаруживать прикосновение через слой пластика (плексигласа) около 1 мм.
Можно слегка разогнать мегу и немного увеличить сопротивление, но лучше этим не увлекаться – стабильность работы разогнанной меги не гарантируется, а слишком большое сопротивление может сравнять ток заряда с током утечки, что сделает сенсор вечно неактивным.
Как бы то ни было, нормального режима работы вполне хватит для прикрытия сенсоров тонким кусочком пластика. Идеальным бы был вариант с напылением токопроводящего покрытия на стекло, но у меня не было особой возможности поэкспериментировать в этом направлении.
Дальше нас ждет код.
В принципе все уже описано выше, но для некоторой ясности приведу код проекта.
В процессе разработки одного проекта мне потребовалась удобная клавиатура на 8 кнопок. Я решил прибегнуть к известному подходу – реализовать емкостные сенсоры.
Физическую теорию я уже описал в своей статье про девайс-сувенир, которой чувствовал, когда его берут в руку (http://habrahabr.ru/blogs/DIY/111627/)
Принцип остается совершенно тем же самым, единственное отличие в реализации – используются не два вывода микроконтроллера а один.
Для начала, видео того, к чему мы будем стремиться:
Шаг 1: Схемотехника
Схемотехника сенсоров слегка изменилась, в связи с тем, что необходимо использовать только одну ногу микроконтроллера на сенсор, а не две. Впрочем, если вам не жалко лишних ног, то можно все оставить по-старому.
![image](https://habrastorage.org/storage/habraeffect/d3/31/d3315275825833172f0420fb5e242ae9.png)
Порядок опроса сенсора будет слегка отличаться. Изначально на пин PD0 подан лог. 0.
Таким образом, ток течет от источника питания через мегаомный резистор и втекает в пин. Если сенсор был заряжен, то ток с него также будет стекать в пин PD0.
В момент опроса мы переключаем пин с выхода на вход (подтяжки отключены!). В этот момент, пин переходит в высокоимпедансное состояние, с сопротивлением порядка нескольких десятков(а то и сотен) МОМ. Ток в направлении пина практически прекращает течь, и начинает течь в сторону сенсора. Как только сенсор зарядится до напряжения свыше уровня лог. 1, данный вход микроконтроллера покажет единицу.
Измерив время, которое прошло с момента перевода PD0 в высокоимпедансное состояние до появления на нем лог 1, можно сделать вывод об изменении емкости сенсора, а значит, отловить момент прикосновения.
Конструктивно сенсор представляет собой прямоугольную контактную площадку 10х10 мм, но на самом деле форма его практически ничем не ограничивается, можно делать круги, змейки, делить их на сегменты и сектора — в общем ввод информации в любом виде.
Ниже представлена фотография законченной печатной платы. Плата заказная, но изготовить такую в домашних условиях также не составляет труда. При использовании макетных плат в роли сенсора может выступать кусочек фольгированного стеклотекстолита, приклеенный поверх платы.
![image](https://habrastorage.org/storage/habraeffect/fb/35/fb3532fa5a1945014ba05a33f9dd09ad.jpg)
Над каждым сенсором расположен SMD диод для индикации нажатия.
Управляется все это микроконтроллером ATMega88, заведенном на частоте 20 МГц.
Таким образом, выход с этой клавиатуры можно сделать любой, какой вам будет нужен и какой потянет мега. В моем случае был удобен SPI (я прошивал и тестил не отключая от программатора, да и в устройстве уже была задействована эта шина), но вы может использовать встроенный в мегу наравне с SPI UART, I2C, или воспользоваться программной реализацией USB от ObjDev. Да, собственно, можно и аппаратной, типа FTDI преобразователя USB->UART.
Итоговая схема (скриншот из Altium) представлена ниже.
![image](https://habrastorage.org/storage/habraeffect/1e/0e/1e0e0d4b59bd52729372be7cb1a80a27.png)
Опять-таки, ничего сложного, ничего лишнего – мега, тактирующая ее цепь, пара сглаживающих питание кондеров, разъем для программирования/подключении, 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);
}