Собираясь в прошлом году в отпуск, решил приобрести автоматическую кормушку для домашних питомцев. Выбор был сделан в пользу изделия брэнда Animal Planet. Уже точно не помню, почему именно была выбрана эта модель, вероятно, на тот момент времени она оптимально сочетала в себе невысокую цену и достаточно хорошие потребительские свойства, представление о которых были составлены по отзывам на веб сайте amazon.com.
Устройство было в скором времени получено по почте, собрано, включено и в него был засыпан корм. Кошки сразу одобрили дизайн, радостно прибегали на шум моторчика и принимались поедать корм еще до того, как он заканчивал высыпаться сверху из бачка. Все бы хорошо, если бы не одно «но». Мне никак не удавалось добиться того, чтобы корм высыпался по установленному расписанию, ради чего собственно и затевалось дело. Но пытливые руки не сдавались и экспериментально было выяснено, что расписание иногда работает, правда, только при условии, что изначально не устанавливалось реальное время на часах. Поняв, что имею дело с явным «багом», обратился к продавцу и тот радостно заверил, что это известная проблема и мне бесплатно заменят устройство более новой версией, лишенной указанной проблемы.
Прошло несколько дней, прислали замену. В «новой версии» прибора «баг» воспроизвелся в полном объеме. К сожалению, времени на разбирательство до отъезда не оставалось и кормление кошек было поручено друзьям. После возвращения, в силу упавшей актуальности, проблема была заброшена и забыта.
Выбирая для себя учебную задачу для ознакомления с микроконтроллерами и платформой Arduino, вспомнил про забытую кормушку. Было решено использовать уже готовый механизм, реализовать систему подачу корма по расписанию, а также проверить теорию условного рефлекса Павлова.
Механизм диспенсера корма устроен таким образом, что на каждый полный поворот крыльчатки приходится 4 замыкания контакта, что соответствует 4 порциям корма. Подсчитывая число замыканий контактов можно определить, сколько корма высыпалось в тарелку.
Прототипирование устройства и отладка производилась на платформе Arduino Uno. Для подачи корма по расписанию на Ebay был приобретен модуль реального времени DS3231 cо встроенной батарейкой, в котором можно запрограммировать два будильника.
Предполагаемый алгоритм будет простым. Контроллер будет периодически просыпаться, чтобы проверить, не сработал какой-либо из запрограммированных будильников, и засыпать дальше для экономии энергии. В случае срабатывания будильника будет проиграна мелодия и высыпано требуемое количество корма. Реализация прототипа на Arduino трудностей не вызвала и ее рассмотрение мы пропустим.
В конструкции донорской кормушки есть отсек для 3-х батареек «D», от которых будет работать конечное устройство. Для уменьшения потребляемой энергии и продления жизни батареек нужно избавиться от лишнего обвеса Arduino. Для разработки конечного устройства был выбран контроллер ATTiny85. Допустимое напряжение питания для него находится в диапазоне 2.7-5.5V, поэтому применения регуляторов напряжения не потребуется и устройство может питаться напрямую от батарейки.
При проектировании конечного устройства были решены следующие задачи, на которых хотелось бы остановиться подробнее:
- Уменьшение потребления энергии. Достигается переводом контроллера в спящий режим и обесточивания ненужных в данный момент цепей, в частности, питание на модуль RTC подается только на время, необходимое для операций с этим модулем. В моей реализации потребляемый устройством ток в режиме ожидания составляет примерно 300 mkA.
- Решение проблемы с недостаточным числом выводов контроллера. Обычно это решается добавлением сдвигового регистра или переназначением функции вывода Reset. В моем случае было возможным использовать один из выводов поочередно для вывода звукового сигнала на динамик и чтения состояния контакта. Кроме того, были использованы перемычки для возможности отключения внутренних цепей устройства при переключении контроллера в режим программирования.
Принципиальная электрическая схема устройства.
Контакты J1-J4, J8-10 служат для подключения Arduino Uno в качестве программатора. Перемычки J5-J7 — для отключения внутренних цепей при программировании Attiny85. В качестве силового элемента в цепи питания электродвигателя используется N-канальный полевой транзистор с изолированным затвором рассчитанным на максимальный ток 500 mA (пусковой ток мотора не превышает 150-200 mA). Очень важно подключить сглаживающий конденсатор (С2 на схеме) большой емкости к VCC. Без него, особенно при питании от батарейки, в момент запуска мотора программа сбивается.
Скетч
#include <DS3232RTC.h> //http://github.com/JChristensen/DS3232RTC
#include <Time.h> //http://playground.arduino.cc/Code/Time
#include <TinyWireM.h> //https://github.com/adafruit/TinyWireM
#include <Narcoleptic.h> //https://code.google.com/p/narcoleptic/
//The library was modified for ATTiny85 as described here:
//http://www.willowdesign.info/blog/digistump-cricket-generator/
const int Note_D = 213;
const int Note_G = 159;
const int Note_A = 142;
const int Note_B = 127;
const int Speaker = 1;
const int feedPin = 1;
const int RTCPowerPin = 4;
const int motorPin = 3;
const int MAX_FEED_TURN = 2; //Dispenced food volume
int feedCounter = 0; // counter for the number of feed revolution
int feedState = HIGH; // current state of the feed contacts
int lastFeedState = HIGH; // previous state of the feed contacts
int reading = HIGH;
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 20; // the debounce time; increase if the output flickers
void setup(void)
{
pinMode(RTCPowerPin, OUTPUT);
digitalWrite(RTCPowerPin, HIGH);
RTC.squareWave(SQWAVE_NONE);
setSyncProvider(RTC.get);
//set the system time to 17h 35m on 22 March 2015
//setTime(17, 35, 0, 22, 3, 2015);
//RTC.set(now());
pinMode(motorPin, OUTPUT);
digitalWrite(motorPin, LOW);
// initialize the feed contact pin as a input:
pinMode(feedPin, INPUT);
}
void loop(void)
{
digitalWrite(motorPin, LOW);
digitalWrite(RTCPowerPin, HIGH); //Turn RTC power ON
delay(50); //ensure RTC is stable after powering it ON
RTC.setAlarm(ALM1_MATCH_HOURS, 0, 0, 7, 0); //morning feed alarm
RTC.setAlarm(ALM2_MATCH_HOURS, 0, 0, 19, 0); //evening feed alarm
digitalWrite(RTCPowerPin, LOW); //Turn RTC power OFF
digitalWrite(Speaker, LOW);
Narcoleptic.delay(20000); // During this time power consumption is minimized
digitalWrite(RTCPowerPin, HIGH);
if (readVcc() < 3500) {
playBatteryLow();
}
delay(50); //ensure RTC is stable after powering it ON
if (RTC.alarm(ALARM_1) || RTC.alarm(ALARM_2)) { //has any of 2 Alarm1s triggered?
//yes, act on the alarm
// Wake up CPU.
feedCounter = 0;
// Wake up the CAT - play some music
playTune();
// Turn ON food dispenser motor
digitalWrite(motorPin, HIGH);
while (feedCounter < MAX_FEED_TURN) {
// read the feed input pin:
reading = digitalRead(feedPin);
// compare the feedState to its previous state
if (reading != lastFeedState) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != feedState) {
feedState = reading;
// if the state has changed, increment the counter
if (feedState == HIGH) {
// if the current state is HIGH then the feed contacts
// went from on to off
feedCounter++;
}
else {
// if the current state is HIGH then the feed contact
// went from on to off:
}
}
}
// save the current state as the last state,
//for next time through the loop
lastFeedState = reading;
}
}
}
void TinyTone(unsigned char divisor, unsigned char octave, unsigned long duration)
{ //http://www.technoblogy.com/show?KVO
//TCCR1 = 0x90 | (8-octave); // for 1MHz clock
TCCR1 = 0x90 | (11 - octave); // for 8MHz clock
OCR1C = divisor - 1; // set the OCR
delay(duration);
TCCR1 = 0x90; // stop the counter
delay(duration * 1.30); // pause between notes
}
void playTune(void)
{
pinMode(Speaker, OUTPUT);
TinyTone(Note_D, 4, 125);
TinyTone(Note_D, 4, 125);
TinyTone(Note_G, 4, 250);
TinyTone(Note_G, 4, 200);
TinyTone(Note_G, 4, 62);
TinyTone(Note_A, 4, 250);
TinyTone(Note_A, 4, 200);
TinyTone(Note_A, 4, 62);
TinyTone(Note_D, 5, 350);
TinyTone(Note_B, 4, 150);
TinyTone(Note_G, 4, 300);
delay(350);
TinyTone(Note_D, 5, 500);
delay(200);
TinyTone(Note_D, 5, 500);
delay(200);
TinyTone(Note_D, 5, 500);
delay(200);
TinyTone(Note_D, 6, 1000);
TCCR1 = 0; // stop the timer
pinMode(Speaker, INPUT);
}
void playBatteryLow(void)
{
pinMode(Speaker, OUTPUT);
TinyTone(Note_D, 4, 125);
TinyTone(Note_D, 6, 250);
TCCR1 = 0; // stop the timer
pinMode(Speaker, INPUT);
}
long readVcc() { //http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
// #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
// #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
// ADMUX = _BV(MUX5) | _BV(MUX0);
// #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
// #else
// ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
// #endif
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long result = (high << 8) | low;
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}
Комментарии к скетчу
В коде широко используются функции и фрагменты кода, найденные на просторах Интернета. Ссылки на источники приводятся в комментариях к коду.
Код для записи реального времени в RTC запускается один раз, затем я его убираю и перезаливаю скетч заново. Часы достаточно точные и коррекции времени в дальнейшем не потребуется.
Контроллер выходит из спящего режима на очень короткое время каждые 20 секунд (вполне можно увеличить то 1 минуты). В это время подается питание на RTC, производится проверка напряжения питания при помощи функции readVcc() и состояние будильников ALARM_1 и ALARM_2. При понижении напряжения ниже установленного порога 3.5 V на динамик выводится короткий звуковой сигнал. Если сработал любой из будильников которые установлены у меня на 7 часов вечера и 7 часов утра, то проигрывается музыкальная мелодия функцией playTune() и включается мотор. При подсчете порций высыпанного корма используется программное подавление дребезга контакта замыкающегося при повороте крыльчатки. Для уменьшения потребления энергии важно остановить вращение мотора в момент размыкания контактов.
Устройство было собрано методом навесного монтажа на небольшой плате размером 5x7 см.
Ну и в заключение кино про котиков: