Сенсорная радиокнопка (Attiny + nRF24 + TTP) - dAshkova

parovoZZ
Offline
Зарегистрирован: 15.02.2016

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

- Эх, мечты!

- Вовсе нет - ответит чей-то голос. 

Давайте мечту превратим в цель, а цель достигнем.

Итак, сформируем наше четкоё ТЗ:

1. Кнопка должна выглядеть хорошо. Как минимум не ублюдочно.

2. Внутреннее убранство должно быть под стать внешнему виду.

3. Количество кнопок ... ну от одной и до четырех. 

4. Кнопки сенсорные.

5. Постановка задачи определила только один источник питания - батарея.

6. Кнопка должна быть максимально тонкой, поэтому определим вид батареи - литиевая.

7. От батареи нам хочется жить годика два, поэтому всё лишнее выключаем.

8. Интерфейс - беспроводной с помощью радиоволн.

9. Мониторинг состояния батареи.

10. Мы согласны с тем, что энергобюджет у нас ограничен, поэтому принимаем неудобства в виде запоздалой реакции на прикосновение.

... 

13. Посылать радиограммы мы будем только по событию - в момент касания и в момент отпуска кнопки. В момент удержания пальца на кнопке - спим. После - выключаем себя. 



Определим участников забега.

МК - Attiny24A (на данный момент Attiny441 из-за ошибки госпожи dAshkova)

За радиоточку в ответе - nRF24l01+

Микросхема сенсора - TTP224B

Батарейка - литиевая CR2032

parovoZZ
Offline
Зарегистрирован: 15.02.2016

Схема

parovoZZ
Offline
Зарегистрирован: 15.02.2016

По ту сторону Луны

 

bizzon
Offline
Зарегистрирован: 29.03.2016

Прикольненько. Это выключатель чего угодно, как я понимаю? 

parovoZZ
Offline
Зарегистрирован: 15.02.2016

bizzon пишет:

Прикольненько. Это выключатель чего угодно, как я понимаю? 

Ага. По коду скоро пройдемся - там всё будет понятно. На тестировании пока.

parovoZZ
Offline
Зарегистрирован: 15.02.2016

Чертим необходимую деталь

и печатаем из фуфлового пластика (на прототип не жалко)))

parovoZZ
Offline
Зарегистрирован: 15.02.2016

И вуаля

Крепим без проводов и на любую поверхность)

parovoZZ
Offline
Зарегистрирован: 15.02.2016

Программная часть.

Сразу после подачи питания на трансивер (не обязательно nRF24 - это может быть любой другой), необходимо сделать паузу для стабилизации частоты осциллятора. По даташиту максимальное время, которое может на это потребоваться, составляет 100 мс. По факту эта пауза будет гораздо короче, но здесь я беру даже с запасом. Для формирования такой раузы я использую таймер вачдога. Нюанс его использования именно в старых аттинях следующий - при первом срабатывании вачдога и разрешенном обработчике прерывания МК уходит на обработчик. Если после этого не поступает сигнал сброса таймера вачдога, то следующее срабатывание приводит к ресету МК. Вместо сброса вачдога также можно заново определить его период работы. Также не забываем про регистр CCP (особенность поздних МК с ядром atmega). Я это опишу в секции макросов:

#define WDT_int_125mS	CCP = 0xD8; WDTCSR = Bit(WDIE) | Bit(WDE) | Bit(WDP1) | Bit(WDP0)	// Включение собаки на 125 мс и разрешение прерываний от нее

Далее нам необходимо определить пин МК, который будет подавать питание через ключ на трансивер и на себя, на выход. Затем конфигурируем секцию SPI, разрешаем прерывания PCINT, считываем параметры из EEPROM, снимаем показания с сенсоров. Перед инициализацией трансивера, нам необходимо выдержать паузу, о которой писал выше. Для этого мы просто уходим в сон. А вачдог нас разбудит. Секция кода, выполняющая перечисленные действия, выглядит так:

uint8_t address = 1;

	PRR =	(1<<PRTWI) | (1<<PRUSART1) | (1<<PRUSART0) |// Тактируем только SPI
		(0<<PRSPI) | (1<<PRTIM2) | (1<<PRTIM1) |
		(1<<PRTIM0) | (1<<PRADC);	

	WDT_int_125mS;				// Взводим будильник на 125 мс

	data[5] = MCUSR;				// Статус перезагрузки
	MCUSR = 0;

	DDRB =	(0<<Q1) | (0<<Q3) |	(1<<Power);	// Конфигурируем пин для питания на выход

	Vcc_On();					// Подаем питание на плату (через ключ)			

	DDRA =	(0<<Q2) | (0<<Q0) |	(1<<nRF_CE) |	// 
		(1<<nRF_CSN) | (1<<SCK_PIN) | (0<<MISO_PIN) |	// 
		(1<<MOSI_PIN) | (0<<nRF_IRQ);		//

	SPI_Init();

	GIMSK = (1<<PCIE1) | (1<<PCIE0);		// Разрешаем прерываание на пинах PCINT...

	PCMSK0 =	(1<<Q0_PCINT) | (1<<Q2_PCINT) |	// Выделяем маской прерывание от кнопок
		(1<<nRF_IRQ_PCINT);			// Выделяем маской прерывание от трансивера
	PCMSK1 = (1<<Q1_PCINT) | (1<<Q3_PCINT);		// Выделяем маской прерывание от кнопок

	Get_Buttons();

	data[0] = EEPROM_read(address++);
	data[1] = EEPROM_read(address++);
	data[2] = EEPROM_read(address);

	sei();
	
	Sleep(SLEEP_MODE_PWR_DOWN);			// Уходим в сон для ожидания запуска трансивера

Обязательное действие в нашей программе - это замер уровня напряжения на батарейке. Для этого мы будем делать замер на внутреннем ИОНе, а в качестве опорного напряжения будем использовать напряжение питания. Но непосредственно перед самим измреением, нам необходимо выдержать паузу в 1 мс для завершения переходных процессов. Чтобы специально этого не делать, я включаю АЦП перед инициализацией трансивера, а замер делаю уже после - как раз проходит заветная 1 мс. АЦП необходимо обязательно выключить - аналоговые цепи даже при отсутствии тактирования потребляют небольшой ток. мы же бромся за каждый наноампер. 

PRR =	(1<<PRTWI) | (1<<PRUSART1) | (1<<PRUSART0) |// Тактируем только ADC и SPI
		(0<<PRSPI) | (1<<PRTIM2) | (1<<PRTIM1) |
		(1<<PRTIM0) | (0<<PRADC);

	ADC_Init();				// Инициализация ADC. Необходимо выдержать 1 мс
						// до начала измерения напряжения питания

	nRF_Init(nRF_channel);			// Конфигурируем регистры и там же
						// включаем RF часть трансивера для выдержки паузы
						// до начала сеанса передачи
	ADC_Start_Conversion();			// Даем команду на измерение напряжения

	WDT_int_8S;				// Взводим вачдог для случая, если не получим
						// прерывания от кнопок

Также не забываем включить вачдог.... на всякий пожарный...

Всё. Подготовительная часть кода закончилась. Далее идет главный цикл. Главный цикл собран по принципу конечного автомата - во сне ждем входного воздействия, отрабатываем его. Если сложилось условие выхода из цикла - просто обесточиваем себя и трансивер. В цепи питания у нас остаётся только микросхема сенсора. 

Пакеты я передаю без запроса подтверждения, т.к. планируется, что получателей будет несколько. Структура пакета выглядит так:



	Структура пакета:
	0 - адрес, старший байт
	1 - адрес, младший байт
	2 - тип
	3 - поле состояния кнопок (Q3 - кнопка0, Q0 - кнопка1, Q2 - кнопка2, Q1 - кнопка3)
	4 - напряжение питания
	5 - доп. сообщение (ошибка)	????
	|__
		0 бит	бит PORF - загрузка по сбросу питания
		1 бит	бит EXTRF - перезагрузка по ресету
		2 бит	бит BORF - перезагрузка по детектору питания
		3 бит	бит WDRF - перезагрузка по вачдогу
		4 бит
		5 бит	
		6 бит	
		7 бит	

Главный цикл:

	cli();

	nRF_Transmit_NOACK(data);			// Отправить данные без подтверждения

	//nRF_STOP();				// Переводим трансивер в StandBy-I

	sei();
	
	//nRF_PowerDown();				// выключаем RF часть трансивера

	Sleep(SLEEP_MODE_PWR_DOWN);			// Уходим в сон	

	Get_Buttons();

	if (data[3] == 0)
	{
		

		//WDT_off;				// Выключаем вычдог

		nRF_PowerUp_interrupt();		// Включаем прерывание по отправке

		data[4] = GPIOR0;

		nRF_Transmit_NOACK(data);		// Отправить данные без подтверждения	
		
	//	nRF_STOP();			// Переводим трансивер в StandBy-I	
						
		Sleep(SLEEP_MODE_PWR_DOWN);		// Уходим в сон

		cli();

		while (1)
		{
			Vcc_Off();		// Всё вырубаем
		}

Функция опроса кнопок:

void Get_Buttons(void)
{
	data[3] = 0;	//~0b11110000;		// Обнуляем поле с кнопками	
	data[3] |= ((PINB & 0b00000001) << 0);		// Считываем кнопку Q3(0) и переносим ее на свое место в массиве
	data[3] |= ((PINA & 0b00000010) >> 0);		// Считываем кнопку Q0(1) и переносим ее на свое место в массиве
	data[3] |= ((PINA & 0b00000001) << 2);		// Считываем кнопку Q2(2) и переносим ее на свое место в массиве
	data[3] |= ((PINB & 0b00000010) << 2);		// Считываем кнопку Q1(3) и переносим ее на свое место в массиве
}

Про иницализацию трансивера и АЦП рассказываю здесь:

http://forum.cxem.net/index.php?/blogs/blog/319-изучаем-usi-на-основе-сверхэкономичного-прототипа/

 

parovoZZ
Offline
Зарегистрирован: 15.02.2016

Полностью собранная плата выглядит так:

Преследовалась главная цель - максимально тонкий дизайн.

Поэтому батарейка 2032, держатель под нее - BS-6-1. Но он ещё не приехал, поэтому на фотографии другой (по высоте чуть выше). Можно и вообще отказаться от держателя в пользу батарейки с усами. В принципе, запаять можно и более емкую 24хх - при не очень интенсивном потоке нажатий работа от батарейки CR2450 растянется примерно на 5 лет. Быстрее выключатель надоест, чем сядет батарейка. Также, вместо танталика должен стоять керамический конденсатор на 10мкФ - он также еще не приехал.

Плата прекрасно помещается вовнутрь корпуса LIVOLO C5-C2-12.

Прилепить можно на любую поверхность. Да хоть и на стекло

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