Третья, заключительная часть моей статьи о создании светящегося кристаллического сувенира. В ней описывается программная часть, то бишь прошивка микроконтроллера 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 флеш-памяти тиньки.
//Состояния девайса 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(); //Включаем интеррапты обратно }
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_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 {
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 мА при полной яркости диода. Даже если мультиметр показал неточный результат и ошибся на порядок, энергии в батарейке должно хватить где-то на полгода простоя, что является вполне неплохим результатом.
При должном старании можно изготовить аналогичный девайс любого размера, да и в качестве соли можно взять вещество другого цвета и вырастить красный или желтый кристалл. Все в ваших руках)