Творим оригинальный подарок при помощи химии физики и электроники: часть 3

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


    Шаг 4: разрабатываем прошивку



    Прошивка для устройства вполне тривиальная, но стоит обратить внимание на следующие моменты:
    1) У данного микроконтроллера всего один 8-битный таймер, так что придется использовать его одновременно и для ШИМа и для опроса сенсора, переключаясь туда-обратно.
    2) Чтобы снизить энергопотребление, было бы неплохо погружать устройство в сон, когда к нему никто не прикасается.

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

    И еще одно примечание: если будете собирать такой девайс и тестить «навесу» запомните следующее:
    сенсор покажет намного бОльшую емкость, если вы при тестировании второй рукой касаетесь, допустим, минуса батарейки. Я с этим накололся, так как при тестировании прижимал провода к батарейке руками, поэтому выставил чересчур большой порог.

    Общий подход к реализации такой: устройство может находится в одном из шести состояний:
    1) Диод не горит, ждем отклика от сенсора. В этом состоянии мы периодически проверяем сенсор, после чего засыпаем примерно на секунду, что обеспечит низкое энергопотребление
    2) После первого отклика от сенсора уже не засыпаем, ждем примерно 300 мс, чтобы убедится, что это не помеха, и перепроверяем сенсор. Если сенсор не активен, то возвращаемся в состояние 1. Иначе переходим к состоянию 3.
    3) Переключаем таймер в режим ШИМа и медленно повышаем скважность, после чего светимся пока не пройдет секунды 4 и переходим в состояние 4
    4) Переключаем таймер из ШИМа в сенсорный режим, проверяем сенсор, чтобы определить, когда девайс поставят на место, когда сенсор становится неактивен, переходим к состоянию 5
    5) Ждем еще секунд 15, перепроверяем сенсор. Если активен, то возвращаемся к состоянию 4. Иначе, переходим к состоянию 6.
    6) Медленно гаснем, при этом, периодически переключая таймер из режима ШИМ в режим сенсора и перепроверяя его активность. Если сенсор вновь стал активен, то переходим к состоянию 3, снова начиная разгораться. Иначе, когда скважность дошла до 0, вновь переходим в режим 1.

    Ниже я изложу код с комментариями. Т.к. с кодом пришлось поторопиться, чтобы успеть к новому году, там вполне могут быть совершенно неоптимальные моменты, прошу за них особо не пинать)
    Тем не менее, это полностью рабочий и отлаженный код. Занимает примерно процентов 70-80 флеш-памяти тиньки.

    #define F_CPU 9600000UL  
    #include<avr/io.h>
    #include<util/delay.h>
    #include<avr/wdt.h>
    #include<avr/sleep.h>
    #include<avr/interrupt.h>
    
    //Состояния девайса
    enum DEV_MODE{M_WAITING_SENSOR,	//Ждем отклика от сенсора и спим
    		     M_SENSOR_RECHECK,	//После первого отклика перепроверяем
    		     M_GLOW,			//Разгораемся
    		     M_GLOW_AND_CHECK,	//Светим и проверяем сенсор
    		     M_GLOW_AND_RECKECK,  //Перепроверяем, если отпустили
    		     M_FADE};			//Гаснем
    		     
    
    unsigned char SensorHi=0;	//Переменная, которая будет хранить значение для активного сенсора
    unsigned short Delay=0;		//Внутренняя переменная для организации долгих задержек
    unsigned short PWM=0;		//Скважность ШИМа
    unsigned char Mode=0; 		//Режим работы
    
    void SetTimer(char Mod) 		//Функция для быстрой переинициализации таймера с режима сенсора на ШИМ
    {
    	if(Mod)			//1,  проверка сенсора
    	{	
    		TCCR0A=0x00;
    		TCCR0B=0x00;
    		TCNT0=0x00;
    	}
    else			//0,  ШИМ
    	{
    		TCCR0A=0x83;
    		TCCR0B=0x00;
    		TCNT0=0x00;
    	}
    }
    
    void Recalibrate()			     //Калибровка сенсора
    {
    	cli();				              //На всякий случай отключаем интеррапты, мало ли что)
    	SetTimer(1);
    	DDRB&= 0b11110111;
    	PORTB|=0b00010000;
    	TCCR0B=0x01;
    	while(!(PINB&0b00001000));
    	TCCR0B=0x00;
    	DDRB|=  0b00001000;
    	PORTB&= 0b11101111;
    	SensorHi=TCNT0+3; 	      //При такой частоте +3 оказалось вполне достаточно, даже слегка много
    	sei();				      //Включаем интеррапты обратно
    }
    
    unsigned char CheckSensor()
    {
    	cli();
    	SetTimer(1);
    	DDRB&= 0b11110111;
    	PORTB|=0b00010000;
    	TCCR0B=0x01;
    	while(!(PINB&0b00001000));
    	TCCR0B=0x00;
    	DDRB|=  0b00001000;
    	PORTB&= 0b11101111;
    	unsigned char Time=TCNT0;
            sei();
    	if(Time>SensorHi)
    		return 0xFF;
    return 0x00;
    }
    
    ISR(SIG_WATCHDOG_TIMEOUT)	//Пустой обработчик прерывания сторожевого таймера
    {
    __asm__ __volatile__("nop");		//Нужен просто чтобы разбудить систему
    }
    
    int main()
    {
    ACSR = 0b1000000;			//Настройка режима энергосбережения
    DDRB  = 0b00011001;			
    SetTimer(1);				//Переключили таймер в режим проверки сенсора
    Mode= M_WAITING_SENSOR;
    sei();
    PORTB=0b00000001;			//Зажигаем диод и ждем 4 секунды
    for(char i=0;i<40;i++)			//Чтобы было понятно, когда именно калибруемся
    {
    	_delay_ms(20);
    	_delay_ms(20);
    	_delay_ms(20);
    	_delay_ms(20);
    	_delay_ms(20);
    }
    PORTB=0b00000000;			//Гасим диод
    Recalibrate();			//Калибруемся
    _delay_ms(20);
    while(1)	
    {
    	switch(Mode)
    	{
    		case M_WAITING_SENSOR:		
    			if(CheckSensor())
    			{
    				Mode= M_SENSOR_RECHECK; 
    				PWM=0x00;
    			}
    			else
    			{		
    				//Здесь инициализируется вотчдог таймер и режим энергосбережения
    				//В процессе отладки пользовался ассемблером, и не стал менять обратно
    				//То же самое можно написать и с использованием библиотечных функций
    				sei();		
    				__asm__ __volatile__("in   r16, 0x21");
    				__asm__ __volatile__("ori r16, 0b00011000");
    				__asm__ __volatile__("out 0x21 ,r16");
    				__asm__ __volatile__("ldi r16, 0b01000111");
    				__asm__ __volatile__("out 0x21 ,r16");
    				__asm__ __volatile__("ldi r16 ,0b00110000");
    				__asm__ __volatile__("out 0x35 ,r16");
    				__asm__ __volatile__("sleep");			
    	                       //сюда мы вернемся уже через 1 секунду, из прерывания
    			}
    		break;
    
    		case M_SENSOR_RECHECK:
    		cli();		
    			Delay++;
    			if(Delay>0x0010)	
    			{
    				if(!CheckSensor())
    				{
    					Mode= M_WAITING_SENSOR; 
    					Delay=0x0000;
    				}
    				else
    				{
    				
    					Delay=0x0000;
    					Mode= M_GLOW;
    					SetTimer(0);
    					PWM=0x00;
    					OCR0A=PWM;
    					TCCR0B|=0x01;					
    				}
    			}
    		break;
    		
    		case  M_GLOW: 
    			Delay++;
    			if((PWM<0xFF)&&(Delay%2==0))   //разгораемся *медленно*, поэтому Delay%2
    				PWM++;
    			if(Delay>0x0200)                            //Полностью разгорелись
    			{	
    				SetTimer(1);
    				PORTB =0b00000001;            //Вместо ШИМа напрямую подаем лог. 1
    				Mode= M_GLOW_AND_CKECK;
    				Delay=0x0000;
    			}
    		break;
    
    	case  M_GLOW_AND_CKECK:	
    		if(!CheckSensor())
    			{
    				Mode=M_GLOW_AND_RECKECK;
    				PWM=0xFF;
    			}
    		break;
    
    	case M_GLOW_AND_RECKECK:
    			Delay++;
    			if(Delay>0x0500)	              //Долгая пауза, чтобы не погаснуть сразу же как отпустят
    			{                                           //а посветить еще секунд 20
    				if(CheckSensor())
    				{
    					Mode= M_GLOW_AND_CKECK; 
    					Delay=0x0000;
    				}
    				else
    				{
    				
    					Delay=0x0000;
    					Mode=M_FADE;
    					PWM=0xFF;
    					OCR0A=PWM;
    					SetTimer(0);
    					TCCR0B=0x01;
    				}
    			}
    		break;
    		
    		case M_FADE:
    			Delay++;
    			if(Delay%5==0)                            //Раз в пять Delay перепроверяем сенсор
    			{
    				TCCR0B=0x00;
    				PORTB =	0b00000000;
    				if(CheckSensor())
    				{
    					Mode=0x02;              //Если что - начинаем снова разгораться
    					Delay=0x0000;
    				}
    				SetTimer(0);
    				TCCR0B=0x01;
    			}
    			if((PWM>0)&&(Delay%2==0))   //Медленно гаснем
    				PWM--;
    			if(Delay>0x0200)                      //Совсем погасли
    			{	
    				SetTimer(1);
    				PORTB=0b00000000;
    				Mode= M_WAITING_SENSOR;
    				Delay=0x0000;
    			}
    		break;
    	}
    OCR0A=PWM;
    _delay_ms(20);                                            
    }
    
    
    }
    


    Заключение



    Вот, собственно и все – дальше осталось дело техники: зашить прошивку в SMD ATTiny13, припаять SMD же резистор к двум ее пинам (возможно, у вас в подставке хватит места для не-SMD компонентов, но у меня место было критично), отведя от него провод в качестве сенсора к фольгированному бортику подставки, подпаять выводы светодиода к микроконтроллеру и общему проводу, плюс от батарейки присоединить к питанию микроконтроллера, а минус к общему проводу и закрыть подставку крышечкой.

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

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

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

    Часть 1
    Часть 2
    Поделиться публикацией
    Комментарии 32
      +7
      Очень интересно, только одно «но».
      Можно заключительную картинку (фотографию) изделия?
      Что получилось в конце?
        +2
        Только видео, увы — подарок уже подарил. Видео в первой части.
          +2
          А где в статье ссылки на предыдущие статьи? Прошу исправить.
            +2
            Исправлено.
          • НЛО прилетело и опубликовало эту надпись здесь
          +1
          Вот спасибо! как раз праздники впереди, а тут и идея подарка есть.
            +1
            Шикарный подарок. Китайцы случаем что-то подобное не делают? 100% купил-бы
              +1
              Не знаю, но в промышленных масштабах такие штуки можно делать за копейки…
              +1
              Очень интересно спасибо. Сам недавно начал писать по АВРки, так что опыта не много. Но могу посоветовать вот что: исключи _delay_ms из кода, замени ее функцией аналогичной из avr/sleep.h за исключением действий с числами с плавающей точной. Если точно задержек не критична конечно +-1-2%, то можно значительно сэкономить на размере (процентов 80 точно). Жаль что у 13 тиньки только один таймер, но зато у нее 4 АЦП. Из-за у меня есть задумка на ее основе сделать автомат один с термистором.
              Кстати вопрос в чем писал/компилял? У меня AVRStudio почему отказалась создавать проект для 13 тиньки на C только на асме.
                0
                >исключи _delay_ms из кода, замени ее функцией аналогичной из avr/sleep.h за исключением действий с числами с плавающей точной.

                Не понял. Во-первых, где тут числа с плавающей точкой? Все в интах. Во-вторых, в avr/sleep.h лежат функции собственно усыпления микроконтроллера (перевода его в спящий режим), а не задержки.
                Они у меня используются, но не тут. В активном режиме меня совершенно не устроит спящий микроконтроллер, он должен продолжать подавать ШИМ-сигнал и проверять сенсоры.

                Писал и компилял в AVR Studio+WinGCC
                  0
                  Упс сорри, дезинформировал немного. Копаем delay.h доходим до util/delay.h
                  Функция собственно:

                  void
                  _delay_ms(double __ms)
                  {
                  uint16_t __ticks;
                  double __tmp = ((F_CPU) / 4e3) * __ms;
                  if (__tmp < 1.0)
                  __ticks = 1;
                  else if (__tmp > 65535)
                  {
                  // __ticks = requested delay in 1/10 ms
                  __ticks = (uint16_t) (__ms * 10.0);
                  while(__ticks)
                  {
                  // wait 1/10 ms
                  _delay_loop_2(((F_CPU) / 4e3) / 10);
                  __ticks --;
                  }
                  return;
                  }
                  else
                  __ticks = (uint16_t)__tmp;
                  _delay_loop_2(__ticks);
                  }

                  Меня здесь например раздражает строчка
                  double __tmp = ((F_CPU) / 4e3) * __ms;
                  Посчитать, округлить и переписать и сравни с какой точностью получится разница.
                    0
                    Буэ, мерзость какая, я и не знал что они внутри аж с даблами работают.
                    Однозначно нафиг, спасибо. В АВРках я только с фикседами работаю, когда необходимы дроби.
                      +2
                      Не за что, дерзай.
                      У меня дочка уже нацеливается кристалл растить, будем искать мешок купороса ;)
                        +1
                        Оптимизация компилятора! Поэтому если поставишь оптимизацию в 0, ничего работать и не будет! Не слушай никого, все сам проверяй! =)
                        Вот что написано в этом же исходнике:
                        «In order for these functions to work as intended, compiler optimizations MUST be enabled, and the delay time MUST be an expression that is a known constant at compile-time.» translate.google.ru/ если непонятно, он весьма адекватно это переводит.

                        И вообще конструкция из толпы делэйев по 20мс мне не понятна, не проще ли написать один сразу на 100мс? Я более чем уверен что код с одним делэйем будет в разы меньше!!!

                        Не верите откомпильте сами:
                        Сначала откомпилен первый вариант а потом второй:

                        При этом сколько внутрь одной делэйки не вбивай, размер не будет меняться!

                        И вообще код очень не читаем, советую пользоваться макро, они облегчат вам жизнь.
                        #ifndef _BV
                        #define _BV(bit) (1 << (bit))
                        #endif

                        #ifndef sbi
                        #define sbi(data, bit) (data) |= _BV(bit)
                        #endif

                        #ifndef cbi
                        #define cbi(data, bit) (data) &= ~_BV(bit)
                        #endif

                        Надеюсь критика достаточна конструктивная. =)
                        Проект весьма хорош! =)
                          0
                          Этот делей лучше вообще убрать, у меня было полное ощущение что они юзают никак не даблы, поэтому большие делеи будут переполняться) Видимо, это мне приснилось)
                            0
                            А, они надеятся на оптимизацию, чтоб в компил-тайме вычислить и округлить…
                            Все равно неприятно осознавать что где-то в глубине кода — даблы)
                      0
                      В качестве компилятора+примитивная среда можно использовать WinAVR. AVRStudio, кстати, после установки WinAVR сама прикрутит в качестве компилятора gcc-avr.
                      0
                      Не совсем понял логику:
                      if((PWM>0)&&(Delay%2==0))   //Медленно гаснем
                      				PWM--;
                      			if(Delay>0x0200)                      //Совсем погасли

                      мы ведь Delay уменьшаем, но проверяем на больше 0x200
                        +1
                        Delay всегда увеличивается. Это просто счетчик времени.
                        Уменьшается PWM, скважность.
                        0
                        действительно, проглядел, извиняюсь (и еще раз о пользе сна)
                          0
                          Так вот прям три части в один день.)

                          Я вот как раз позавчера вырастил два кристала из медного купороса.)
                            +1
                            Сейчас еще одна статья будет, ждите)
                            –1
                            Как же я завидую людям, у которых руки не из жопы :)
                              0
                              Кстати, вместо затравки можно использовать медную проволку.
                              Я ж решил немного поиздеваться: выращиваю ярко-зеленый кристалл. :)
                                0
                                Зеленый каким образом? Делитесь секретом! Я, например, вообще далек от этого(один раз в школе пробовал вырастить и ничего не вышло), но попробовать хочется :)
                                  0
                                  Для зеленого не знаю солей (в самом деле, из чего вырастить ярко-зеленый?) а вот красно-оранжевый выращивается из красной кровяной соли. И выглядят очень неплохо, судя по фото (сам не выращивал). Говорят, они намного стабильнее кристаллов купороса.

                                    0

                                    вот
                                      0
                                      Медный купорос и обычная пищевая соль. Последняя должна преобладать. =)
                                        0
                                        А не повлияет ли это на структуру кристалла? Мне что-то сомнительно, что все пройдет удачно.
                                          0
                                          Не повлияет, если вы хорошо растворите пищевую соль, которую надо растворять последней (на крайняк, она может плавать на дне).

                                          У меня уже вырастают первые кристаллы, фотку прилагаю.
                                          dl.dropbox.com/u/15528316/IMG_0086.JPG

                                          Качество очень плохое, но под рукой нет фотоаппарата.
                                    0
                                    =) Да идея крутая. Автору спасибо. Пошел за медным купорос =)
                                      0
                                      Для зеленого цвета кристаллов используйте NiSO4 * 7H2O.
                                      Получаются великолепные изумрудные кристаллы.

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                      Самое читаемое