// required for WDTO_8S
78 |
79 | #define VERSION "1.3.3"
80 | /*
81 | * Version 1.3.3
82 | * - 3. party libs.
83 | * Version 1.3.2
84 | * - Adapted MCUSR handling.
85 | * Version 1.3.1
86 | * - Check for closed window happens only the first 10 minutes of alarm.
87 | * Version 1.3.0
88 | * - Changed voltage low detection.
89 | * - Improved DEBUG output.
90 | * Version 1.2.2
91 | * - Converted to Serial.print.
92 | * - New PWMTone() without tone().
93 | * Version 1.2.1
94 | * - Fixed bug in check for temperature rising after each alarm.
95 | * Version 1.2
96 | * - Improved sleep, detecting closed window also after start of alarm, reset behavior.
97 | * - Changed LIPO detection threshold.
98 | * - Fixed analog reference bug.
99 | */
100 |
101 | #if defined(ALARM_TEST)
102 | #define ALARM_TEST_PIN PB0
103 | #endif
104 |
105 | #define OPEN_WINDOW_ALARM_DELAY_MINUTES 5 // Wait time between window open detection and activation of alarm
106 | const int OPEN_WINDOW_ALARM_FREQUENCY_HIGH = 2200; // Should be the resonance frequency of speaker/buzzer
107 | const int OPEN_WINDOW_ALARM_FREQUENCY_LOW = 1100;
108 | const int OPEN_WINDOW_ALARM_FREQUENCY_VCC_TOO_LOW = 1600; // Use a different frequency to distinguish the this alert from others
109 |
110 | /*
111 | * Temperature timing
112 | */
113 | const uint16_t TEMPERATURE_SAMPLE_SECONDS = 24; // Use multiple of 8 here
114 | const uint8_t OPEN_WINDOW_SAMPLES = (OPEN_WINDOW_ALARM_DELAY_MINUTES * 60) / TEMPERATURE_SAMPLE_SECONDS;
115 | const uint8_t TEMPERATURE_COMPARE_AMOUNT = 2; // compare 2 values
116 | const uint8_t TEMPERATURE_COMPARE_DISTANCE = 8; // 3 minutes and 12 seconds
117 | // 2. compare for slower decreasing temperatures
118 | const uint8_t TEMPERATURE_COMPARE_2_DISTANCE = (4 * TEMPERATURE_COMPARE_DISTANCE); // 12 minutes and 48 seconds
119 |
120 | // Array to hold enough values to compare TEMPERATURE_COMPARE_AMOUNT values with the same amount of values TEMPERATURE_COMPARE_DISTANCE positions before
121 | const uint8_t TEMPERATURE_ARRAY_SIZE = TEMPERATURE_COMPARE_AMOUNT + TEMPERATURE_COMPARE_DISTANCE + TEMPERATURE_COMPARE_AMOUNT;
122 | uint16_t sTemperatureArray[TEMPERATURE_ARRAY_SIZE]; // value at 0 is newest one
123 |
124 | /*
125 | * Temperature values
126 | */
127 | const uint16_t TEMPERATURE_DELTA_THRESHOLD_DEGREE = 2; // 1 LSB = 1 Degree Celsius
128 | const uint16_t TEMPERATURE_DELTA_2_THRESHOLD_DEGREE = (2 * TEMPERATURE_DELTA_THRESHOLD_DEGREE); // 1 LSB = 1 Degree Celsius
129 | uint16_t sTemperatureNewSum = 0;
130 | uint16_t sTemperatureOldSum = 0;
131 |
132 | uint16_t sTemperatureMinimumAfterWindowOpen; // for window close detection
133 | uint16_t sTemperatureAtWindowOpen;
134 |
135 | /*
136 | * Detection flags
137 | */
138 | bool sOpenWindowDetected = false;
139 | bool sOpenWindowDetectedOld = false;
140 | uint8_t sOpenWindowSampleDelayCounter;
141 |
142 | /*
143 | * VCC monitoring
144 | */
145 | const uint16_t VCC_VOLTAGE_USB_DETECTION = 4300; // Above 4.3 volt we assume that USB is attached.
146 | const uint16_t VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_LIPO = 3550; // 3.7 volt is the normal operating voltage if powered by a LiPo battery
147 | const uint16_t VCC_VOLTAGE_LIPO_DETECTION = 3450; // Above 3.45 volt we assume that a LIPO battery is attached, below we assume a CR2032 or two AA or AAA batteries are attached.
148 | const uint16_t VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_STANDARD = 2600; // 3.0 volt is normal operating voltage if powered by a CR2032 or two AA or AAA batteries.
149 | const uint16_t VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_STANDARD_BOD2_7 = 2800; // BOD is at 2.7 volt typically, so we cannot get values below that
150 |
151 | uint16_t sVCCVoltageMillivolt;
152 | bool sVCCVoltageTooLow;
153 | bool sLIPOSupplyDetected;
154 | bool sBODLevelIsBelow2_7;
155 | uint8_t getBODLevelFuses();
156 | bool isBODSFlagExistent();
157 | void checkVCCPeriodically();
158 |
159 | const uint8_t VCC_MONITORING_DELAY_MIN = 60; // Check VCC every hour, because this costs extra power.
160 | uint16_t sVCCMonitoringDelayCounter; // Counter for VCC monitoring.
161 |
162 | //
163 | // ATMEL ATTINY85
164 | //
165 | // +-\/-+
166 | // RESET/ADC0 (D5) PB5 1| |8 Vcc
167 | // Tone USB+ ADC3 (D3) PB3 2| |7 PB2 (D2) INT0/ADC1 - TX Debug output
168 | // Tone inv. USB- ADC2 (D4) PB4 3| |6 PB1 (D1) MISO/DO/AIN1/OC0B/OC1A/PCINT1 - (Digispark) LED
169 | // GND 4| |5 PB0 (D0) OC0A/AIN0 - Alarm Test if connected to ground
170 | // +----+
171 |
172 | #define LED_PIN PB1
173 | #define TONE_PIN PB4
174 | #define TONE_PIN_INVERTED PB3
175 |
176 | #define ADC_TEMPERATURE_CHANNEL_MUX 15
177 | #define ADC_1_1_VOLT_CHANNEL_MUX 12
178 | // 0 1 0 Internal 1.1 volt voltage Reference.
179 | #define INTERNAL (2)
180 | #define INTERNAL1V1 INTERNAL
181 | #define SHIFT_VALUE_FOR_REFERENCE REFS0
182 |
183 | #if (LED_PIN == TX_PIN)
184 | #error LED pin must not be equal TX pin.
185 | #endif
186 |
187 | #define LED_PULSE_LENGTH 200 // 500 is well visible, 200 is OK
188 | #if (LED_PULSE_LENGTH < 150)
189 | #error LED_PULSE_LENGTH must at least be 150, since the code after digitalWrite(LED_PIN, 1) needs 150 us.
190 | #endif
191 |
192 | uint8_t sMCUSRStored; // content of MCUSR register at startup
193 |
194 | void PWMtone(unsigned int aFrequency, unsigned int aDurationMillis = 0);
195 | void delayAndSignalOpenWindowDetectionAndLowVCC();
196 | void alarm();
197 | void playDoubleClick();
198 | void readTempAndManageHistory();
199 | void resetHistory();
200 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler);
201 | void sleepDelay(uint16_t aSecondsToSleep);
202 | void delayMilliseconds(unsigned int aMillis);
203 | uint16_t readADCChannelWithReferenceOversample(uint8_t aChannelNumber, uint8_t aReference, uint8_t aOversampleExponent);
204 | uint16_t getVCCVoltageMillivolt(void);
205 |
206 | #if defined(DEBUG)
207 | void printFuses(void);
208 | void printBODSFlagExistence();
209 | #endif
210 |
211 | #define STR_HELPER(x) #x
212 | #define STR(x) STR_HELPER(x)
213 |
214 | /***********************************************************************************
215 | * Code starts here
216 | ***********************************************************************************/
217 |
218 | void setup() {
219 | /*
220 | * store MCUSR early for later use
221 | */
222 | if (MCUSR != 0) {
223 | sMCUSRStored = MCUSR; // content of MCUSR register at startup
224 | MCUSR = 0; // to prepare for next boot.
225 | } else {
226 | sMCUSRStored = GPIOR0; // Micronucleus puts a copy here
227 | GPIOR0 = 0; // Clear it to detect a jmp 0
228 | }
229 |
230 | #if defined(DEBUG)
231 | /*
232 | * Initialize the serial pin as an output for Serial.print like debugging
233 | */
234 | initTXPin();
235 | #endif
236 |
237 | /*
238 | * initialize the pins
239 | */
240 | pinMode(LED_PIN, OUTPUT);
241 | pinMode(TONE_PIN_INVERTED, OUTPUT);
242 | pinMode(TONE_PIN, OUTPUT);
243 | #if defined(ALARM_TEST)
244 | pinMode(ALARM_TEST_PIN, INPUT_PULLUP);
245 | #endif
246 |
247 | sBODLevelIsBelow2_7 = (getBODLevelFuses() >= 6);
248 |
249 | #if defined(DEBUG)
250 | Serial.println(F("START " __FILE__ "\nVersion " VERSION " from " __DATE__ "\nAlarm delay = " STR(OPEN_WINDOW_ALARM_DELAY_MINUTES) " minutes"));
251 |
252 | Serial.print(F("Brown Out Detection is "));
253 | if (getBODLevelFuses() == 7) {
254 | Serial.print(F("disabled => 6"));
255 | } else {
256 | Serial.print(F("enabled => "));
257 | if (isBODSFlagExistent()) {
258 | // Can disable BOD for sleep
259 | Serial.print(F("6"));
260 | } else {
261 | Serial.print(F("25"));
262 | }
263 | }
264 | Serial.println(F(" micro Ampere sleep current"));
265 | #endif
266 |
267 | #if defined(TRACE)
268 | Serial.print(F("MCUSR=0x"));
269 | Serial.println(sMCUSRStored, HEX);
270 | Serial.print(F("WDTCR=0x"));
271 | Serial.println(WDTCR, HEX);
272 | Serial.print(F("OSCCAL=0x"));
273 | Serial.println(OSCCAL, HEX);
274 | printBODSFlagExistence();
275 | printFuses();
276 | #endif
277 |
278 | /*
279 | * init sleep mode and wakeup period
280 | */
281 | initPeriodicSleepWithWatchdog(SLEEP_MODE_PWR_DOWN, WDTO_8S);
282 |
283 | /*
284 | * Initialize ADC channel and reference
285 | */
286 | readADCChannelWithReferenceOversample(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL1V1, 0);
287 |
288 | /*
289 | * Signal power on with a single tone or signal reset with a double click.
290 | */
291 | #if defined(DEBUG)
292 | Serial.print(F("Booting from "));
293 | #endif
294 | if (sMCUSRStored & _BV(PORF)) {
295 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_HIGH, 100);
296 | #if defined(DEBUG)
297 | Serial.println(F("power up"));
298 | #endif
299 | } else {
300 | playDoubleClick();
301 | #if defined(DEBUG)
302 | Serial.println(F("reset"));
303 | #endif
304 | }
305 |
306 | /*
307 | * Blink LED at startup to show OPEN_WINDOW_MINUTES
308 | */
309 | delayMilliseconds(1000); // wait extra second after bootloader blink
310 | for (int i = 0; i < OPEN_WINDOW_ALARM_DELAY_MINUTES; ++i) {
311 | // activate LED
312 | digitalWrite(LED_PIN, 1);
313 | delayMilliseconds(200); // delay() is disabled, so use delayMicroseconds()
314 | // deactivate LED
315 | digitalWrite(LED_PIN, 0);
316 | delayMilliseconds(200);
317 | }
318 |
319 | #if defined(ALARM_TEST)
320 | if (!digitalRead(ALARM_TEST_PIN)) {
321 | #if defined(DEBUG)
322 | Serial.println(F("Test signal out"));
323 | #endif
324 | alarm();
325 | }
326 | #endif
327 |
328 | /*
329 | * Check VCC and decide if LIPO or 2 standard batteries / 1 button cell attached
330 | */
331 | sVCCVoltageMillivolt = getVCCVoltageMillivolt();
332 | if (sVCCVoltageMillivolt > VCC_VOLTAGE_LIPO_DETECTION) {
333 | sLIPOSupplyDetected = true;
334 | } else {
335 | sLIPOSupplyDetected = false;
336 | }
337 |
338 | sVCCMonitoringDelayCounter = 1; // 1 -> check directly now
339 | checkVCCPeriodically();
340 |
341 | // disable digital input buffer to save power
342 | // do not disable buffer for outputs whose values are read back
343 | DIDR0 = _BV(ADC1D) | _BV(ADC2D) | _BV(ADC3D) | _BV(AIN1D) | _BV(AIN0D);
344 |
345 | // This disables Arduino delay() and millis() timer 0 and also its interrupts which kills the deep sleep.
346 | PRR = _BV(PRTIM0) | _BV(PRUSI); // Disable timer 0 and USI - has no effect on Power Down current
347 |
348 | /*
349 | * wait 8 seconds, since ATtinys temperature is increased after the micronucleus boot process
350 | * We do not disable ADC here, so we consume 212 uA
351 | */
352 | sleep_cpu()
353 | ;
354 | }
355 |
356 | /*
357 | * Shift temperature history values ad insert new value.
358 | * Check if temperature decreases after power on.
359 | * Check if window was just opened.
360 | * If window was opened check if window still open -> ALARM
361 | * Loop needs 2.1 ms and with DEBUG 6.5 ms => active time is ca. 1/10k or 1/4k of total time and power consumption is 500 times more than sleep.
362 | * The sleep wakeup time for PLL clock
363 | * 2 ms for temperature reading
364 | * 0.25 ms for processing
365 | * 0.05 ms for LED flashing
366 | * + 4.4 ms for DEBUG
367 | */
368 | void loop() {
369 |
370 | readTempAndManageHistory(); // needs 2 milliseconds
371 |
372 | // activate LED after reading to signal it. Do it here to reduce delay below.
373 | digitalWrite(LED_PIN, 1);
374 |
375 | /*
376 | * Check if we are just after boot and temperature is decreasing
377 | */
378 | if ((sTemperatureArray[TEMPERATURE_ARRAY_SIZE - 1] == 0) && (sTemperatureArray[TEMPERATURE_ARRAY_SIZE - 2] > 0)
379 | /*
380 | * array is almost full, so check if temperature is lower than at boot time which means,
381 | * we ported the sensor from a warm place to its final one
382 | * or the window is still open and the user has pushed the reset button to avoid an alarm.
383 | */
384 | && (sTemperatureArray[0] < sTemperatureArray[TEMPERATURE_ARRAY_SIZE - 2])) {
385 | // Start from beginning, clear temperature array
386 | #if defined(DEBUG)
387 | Serial.println(F("Detected porting to a colder place -> reset"));
388 | #endif
389 | resetHistory();
390 | } else {
391 |
392 | if (!sOpenWindowDetected) {
393 | /*
394 | * Check if window just opened
395 | */
396 | // tTemperatureOldSum can be 0 -> do not use tTemperatureNewSum < tTemperatureOldSum - (TEMPERATURE_DELTA_THRESHOLD_DEGREE * TEMPERATURE_COMPARE_AMOUNT)
397 | if (sTemperatureNewSum + (TEMPERATURE_DELTA_THRESHOLD_DEGREE * TEMPERATURE_COMPARE_AMOUNT) < sTemperatureOldSum) {
398 | #if defined(DEBUG)
399 | Serial.println(F("Detected window just opened -> check again in " STR(OPEN_WINDOW_ALARM_DELAY_MINUTES) " minutes"));
400 | #endif
401 | sTemperatureMinimumAfterWindowOpen = sTemperatureNewSum;
402 | sTemperatureAtWindowOpen = sTemperatureNewSum;
403 | sOpenWindowDetected = true;
404 | sOpenWindowSampleDelayCounter = 0;
405 | }
406 | } else {
407 | /*
408 | * Here open window is detected
409 | * First check if window already closed -> start a new detection
410 | */
411 | if (sTemperatureNewSum > (sTemperatureMinimumAfterWindowOpen + TEMPERATURE_COMPARE_AMOUNT)) {
412 | sOpenWindowDetected = false;
413 | #if defined(DEBUG)
414 | Serial.println(F("Detected window already closed -> start again"));
415 | #endif
416 | // reset history in order to avoid a new detection at next sample, since tTemperatureNewSum may still be lower than tTemperatureOldSum
417 | resetHistory();
418 | } else {
419 | if (sTemperatureNewSum < sTemperatureMinimumAfterWindowOpen) {
420 | // set new minimum temperature
421 | sTemperatureMinimumAfterWindowOpen = sTemperatureNewSum;
422 | }
423 |
424 | /*
425 | * Check if alarm delay was reached
426 | */
427 | sOpenWindowSampleDelayCounter++;
428 | if (sOpenWindowSampleDelayCounter >= OPEN_WINDOW_SAMPLES) {
429 | /*
430 | * Here delay is reached
431 | * Check if still open - current temperature must be 1 degree lower than temperature at time of open detection
432 | * "- TEMPERATURE_COMPARE_AMOUNT": this reduces the sensibility, but helps to detect already closed windows.
433 | * You may remove this to increase sensibility.
434 | */
435 | if (sTemperatureNewSum <= sTemperatureAtWindowOpen - TEMPERATURE_COMPARE_AMOUNT) {
436 | /*
437 | * Window is still open -> ALARM
438 | */
439 | #if defined(DEBUG)
440 | Serial.println(F("Detected window still open -> alarm"));
441 | #endif
442 | alarm();
443 | } else {
444 | // Temperature not 1 degree lower than temperature at time of open detection
445 | sOpenWindowDetected = false;
446 | #if defined(DEBUG)
447 | Serial.println(F("Assume wrong window open detection -> start again"));
448 | #endif
449 | }
450 | } // delay
451 | } // already closed
452 | } // !sOpenWindowDetected
453 | } // after power on and temperature is decreasing
454 |
455 | /*
456 | * VCC check every hour
457 | */
458 | checkVCCPeriodically(); // needs 4.5 ms
459 |
460 | delayAndSignalOpenWindowDetectionAndLowVCC(); // Introduce a delay of 22 ms if open window is detected to let the LED light longer
461 | // deactivate LED before sleeping
462 | digitalWrite(LED_PIN, 0);
463 |
464 | sleepDelay(TEMPERATURE_SAMPLE_SECONDS);
465 | }
466 |
467 | /*
468 | * Like tone(), but use OC1B (PB4) and (inverted) !OC1B (PB3)
469 | */
470 | void PWMtone(unsigned int aFrequency, unsigned int aDurationMillis) {
471 |
472 | // Determine which prescaler to use, we are running with 1 MHz now
473 | uint8_t tPrescaler = 0x01;
474 | uint16_t tOCR = 1000000 / aFrequency; // cannot use F_CPU since clock is set to 1 MHz at runtime
475 | while (tOCR > 0x100 && tPrescaler < 0x0F) {
476 | tPrescaler++;
477 | tOCR >>= 1;
478 |
479 | }
480 |
481 | OCR1C = tOCR - 1; // The frequency of the PWM will be Timer Clock 1 Frequency divided by (OCR1C value + 1).
482 | OCR1B = OCR1C / 2; // set PWM to 50%
483 | GTCCR = _BV(PWM1B) | _BV(COM1B0); // Switch to PWM Mode with OC1B (PB4) + !OC1B (PB3) outputs enabled
484 | TCCR1 = (tPrescaler << CS10);
485 |
486 | delayMilliseconds(aDurationMillis);
487 | TCCR1 = 0; // Select no clock
488 | GTCCR = 0; // Disconnect OC1B + !OC1B
489 | digitalWrite(TONE_PIN_INVERTED, LOW);
490 | digitalWrite(TONE_PIN, LOW);
491 | }
492 |
493 | /*
494 | * plays alarm signal for the specified seconds
495 | */
496 | void playAlarmSignalSeconds(uint16_t aSecondsToPlay) {
497 | #if defined(DEBUG)
498 | Serial.print(F("Play alarm for "));
499 | Serial.print(aSecondsToPlay);
500 | Serial.println(F(" seconds"));
501 | #endif
502 | uint16_t tCounter = (aSecondsToPlay * 10) / 13; // == ... * 1000 (ms per second) / (1300 ms for a loop)
503 | if (tCounter == 0) {
504 | tCounter = 1;
505 | }
506 | while (tCounter-- != 0) {
507 | // activate LED
508 | digitalWrite(LED_PIN, 1);
509 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_LOW, 300);
510 |
511 | // deactivate LED
512 | digitalWrite(LED_PIN, 0);
513 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_HIGH, 1000);
514 | }
515 | }
516 |
517 | void resetHistory() {
518 | for (uint8_t i = 0; i < TEMPERATURE_ARRAY_SIZE - 1; ++i) {
519 | sTemperatureArray[i] = 0;
520 | }
521 | }
522 |
523 | void readTempAndManageHistory() {
524 | sTemperatureNewSum = 0;
525 | sTemperatureOldSum = 0;
526 | uint8_t tIndex = TEMPERATURE_ARRAY_SIZE - 1;
527 | /*
528 | * shift values in temperature history array and insert new one at [0]
529 | */
530 | while (tIndex >= TEMPERATURE_COMPARE_AMOUNT + TEMPERATURE_COMPARE_DISTANCE) {
531 | // shift TEMPERATURE_COMPARE_AMOUNT values to end and sum them up
532 | sTemperatureArray[tIndex] = sTemperatureArray[tIndex - 1];
533 | sTemperatureOldSum += sTemperatureArray[tIndex - 1];
534 | tIndex--;
535 | }
536 | while (tIndex >= TEMPERATURE_COMPARE_AMOUNT) {
537 | // shift values to end
538 | sTemperatureArray[tIndex] = sTemperatureArray[tIndex - 1];
539 | tIndex--;
540 | }
541 | while (tIndex > 0) {
542 | // shift (TEMPERATURE_COMPARE_AMOUNT - 1) values to end and sum them up
543 | sTemperatureArray[tIndex] = sTemperatureArray[tIndex - 1];
544 | sTemperatureNewSum += sTemperatureArray[tIndex - 1];
545 | tIndex--;
546 | }
547 | /*
548 | * Read new Temperature 16 times (typical 280 - 320 at 25 C) and add to sum
549 | * needs 2 ms
550 | */
551 | sTemperatureArray[0] = readADCChannelWithReferenceOversample(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL1V1, 4);
552 | sTemperatureNewSum += sTemperatureArray[0];
553 |
554 | #if defined(DEBUG)
555 | // needs 4.4 ms
556 | Serial.print(F("Temp="));
557 | Serial.print(sTemperatureArray[0]);
558 | Serial.print(F(" Old="));
559 | Serial.print(sTemperatureOldSum);
560 | Serial.print(F(" New="));
561 | Serial.println(sTemperatureNewSum);
562 | #endif
563 | }
564 |
565 | /*
566 | * Check if history is completely filled and if temperature is rising
567 | */
568 | bool checkForTemperatureRising() {
569 | if (sTemperatureArray[TEMPERATURE_ARRAY_SIZE - 1] != 0
570 | && sTemperatureNewSum > sTemperatureOldSum + (TEMPERATURE_DELTA_THRESHOLD_DEGREE * TEMPERATURE_COMPARE_AMOUNT)) {
571 | #if defined(DEBUG)
572 | Serial.println(F("Alarm - detected window already closed -> start again"));
573 | #endif
574 | sOpenWindowDetected = false;
575 | resetHistory();
576 | return true;
577 | }
578 | return false;
579 | }
580 |
581 | /*
582 | * Generates a 2200 | 1100 Hertz tone signal for 600 seconds / 10 minutes and then play it 10 seconds with intervals starting from 24 seconds up to 5 minutes.
583 | * After 2 minutes the temperature is checked for the remaining 8 minutes if temperature is increasing in order to detect a closed window.
584 | * Check temperature at each end of break interval to discover closed window, if window was closed during the silent break, but device was not reset.
585 | */
586 | void alarm() {
587 |
588 | // First 120 seconds - just generate alarm tone
589 | playAlarmSignalSeconds(120);
590 | // after 80 seconds the new (increased) temperature is stable
591 |
592 | // prepare for new temperature check - reset history
593 | #if defined(DEBUG)
594 | Serial.println(F("After 120 seconds prepare for new temperature check -> reset history"));
595 | Serial.println(F("Play alarm for 480 seconds and check for rising temperature every 30 seconds"));
596 | #endif
597 | resetHistory();
598 |
599 | // remaining 480 seconds - check temperature while generating alarm tone
600 | for (uint8_t i = 0; i < 16; ++i) {
601 | readTempAndManageHistory();
602 | /*
603 | * Check if history is completely filled and if temperature is rising
604 | */
605 | if (checkForTemperatureRising()) {
606 | return;
607 | }
608 | playAlarmSignalSeconds(30);
609 | }
610 |
611 | #if defined(DEBUG)
612 | Serial.println(F("After 10 minutes continuous alarm play it now for 10 seconds with increasing delay starting at 24 seconds"));
613 | #endif
614 |
615 | uint16_t tDelay = 24; // begin with 24 s, end at 600 s (5 minutes)
616 | /*
617 | * Do not check for rising temperature here, since it may break a valid alarm.
618 | * The alarm last for 10 minutes now and no rising temperature could be detected in this time, so it makes no sense here.
619 | */
620 | while (true) {
621 | #if defined(DEBUG)
622 | Serial.print(F("Alarm pause for "));
623 | Serial.print(tDelay);
624 | Serial.println(F(" seconds"));
625 | #endif
626 | sleepDelay(tDelay); // Start with 24 seconds
627 | playAlarmSignalSeconds(10);
628 | noTone(TONE_PIN);
629 | if (tDelay < 600) { // up to 5 minutes
630 | tDelay += tDelay / 16;
631 | }
632 |
633 | }
634 | }
635 |
636 | void playDoubleClick() {
637 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_HIGH, 2);
638 | delayMilliseconds(100); // delay between clicks
639 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_HIGH, 2);
640 | }
641 |
642 | /*
643 | * Flash LED only for a short period to save power.
644 | * If open window detected, increase pulse length to give a visual feedback
645 | */
646 | void delayAndSignalOpenWindowDetectionAndLowVCC() {
647 | if (sOpenWindowDetected) {
648 | sOpenWindowDetectedOld = true;
649 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_HIGH, 2); // 2 ms can be heard as a click
650 | delayMicroseconds(20000); // to let the led light longer
651 |
652 | } else if (sOpenWindowDetectedOld) {
653 | // closing window just detected -> signal it with 2 clicks
654 | sOpenWindowDetectedOld = false; // do it once
655 | playDoubleClick();
656 |
657 | } else if (sVCCVoltageTooLow) {
658 | PWMtone(OPEN_WINDOW_ALARM_FREQUENCY_VCC_TOO_LOW, 40); // Use also a different frequency to distinguish this alert from others
659 | } else {
660 | delayMicroseconds(LED_PULSE_LENGTH - 150); // - 150 for the duration from digitalWrite(LED_PIN, 1) until here
661 | }
662 | }
663 |
664 | /*
665 | * In Power Down sleep mode we have the watchdog running and ADC disabled, but powered.
666 | * This needs 5.6 uA.
667 | * If BOD is enabled by fuses, we need additionally 20 uA resulting in 26 uA current.
668 | */
669 | void sleepDelay(uint16_t aSecondsToSleep) {
670 | ADCSRA = 0; // disable ADC -> saves 200 uA
671 | for (uint16_t i = 0; i < (aSecondsToSleep / 8); ++i) {
672 | /*
673 | * Turn off the brown-out detector - but this works only for ATtiny85 revision C, which is hardly seen in the wild :-(.
674 | * It can save additional 20 uA if BOD is enabled by fuses
675 | */
676 | sleep_bod_disable()
677 | ;
678 | // uint8_t tMcucrValue = MCUCR | _BV(BODS) | _BV(BODSE); // set to one
679 | // MCUCR = tMcucrValue; // set both flags to one
680 | // MCUCR = tMcucrValue & ~_BV(BODSE); // reset BODSE within 4 clock cycles
681 | sleep_cpu()
682 | ;
683 | }
684 | }
685 |
686 | void delayMilliseconds(unsigned int aMillis) {
687 | for (unsigned int i = 0; i < aMillis; ++i) {
688 | delayMicroseconds(1000);
689 | }
690 | }
691 |
692 | #define ADC_PRESCALE8 3 // 104 microseconds per ADC conversion at 1 MHz
693 | uint16_t readADCChannelWithReferenceOversample(uint8_t aChannelNumber, uint8_t aReference, uint8_t aOversampleExponent) {
694 | uint16_t tSumValue = 0;
695 | ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
696 |
697 | // ADCSRB = 0; // free running mode if ADATE is 1 - is default
698 | // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag
699 | ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE8);
700 |
701 | for (uint8_t i = 0; i < _BV(aOversampleExponent); i++) {
702 | /*
703 | * wait for free running conversion to finish.
704 | * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion.
705 | */
706 | loop_until_bit_is_set(ADCSRA, ADIF);
707 |
708 | ADCSRA |= _BV(ADIF); // clear bit to recognize conversion has finished
709 | // Add value
710 | tSumValue += ADCL | (ADCH << 8);
711 | // tSumValue += (ADCH << 8) | ADCL; // this does NOT work!
712 | }
713 | ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode)
714 | return (tSumValue >> aOversampleExponent);
715 | }
716 |
717 | uint16_t getVCCVoltageMillivolt(void) {
718 | // use AVCC with external capacitor at AREF pin as reference
719 | uint8_t tOldADMUX = ADMUX;
720 | /*
721 | * Must wait >= 200 us if reference has to be switched to VSS
722 | * Must wait >= 70 us if channel has to be switched to ADC_1_1_VOLT_CHANNEL_MUX
723 | */
724 | if ((ADMUX & (INTERNAL << SHIFT_VALUE_FOR_REFERENCE)) || ((ADMUX & 0x0F) != ADC_1_1_VOLT_CHANNEL_MUX)) {
725 | // switch AREF
726 | ADMUX = ADC_1_1_VOLT_CHANNEL_MUX | (DEFAULT << SHIFT_VALUE_FOR_REFERENCE);
727 | // and wait for settling
728 | delayMicroseconds(400); // experimental value is > 200 us
729 | }
730 | uint16_t tVCC = readADCChannelWithReferenceOversample(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 2);
731 | ADMUX = tOldADMUX;
732 | /*
733 | * Do not wait for reference to settle here, since it may not be necessary
734 | */
735 | return ((1024L * 1100) / tVCC);
736 | }
737 |
738 | /*
739 | * called every hour
740 | * needs 4.5 ms
741 | */
742 | void checkVCCPeriodically() {
743 | sVCCMonitoringDelayCounter--;
744 | if (sVCCMonitoringDelayCounter == 0) {
745 | sVCCVoltageMillivolt = getVCCVoltageMillivolt();
746 | #if defined(DEBUG)
747 | Serial.print(F("VCC="));
748 | Serial.print(sVCCVoltageMillivolt);
749 | Serial.print(F("mV - "));
750 | if (sVCCVoltageMillivolt > VCC_VOLTAGE_USB_DETECTION) {
751 | Serial.print(F("USB"));
752 | } else {
753 | if (sLIPOSupplyDetected) {
754 | Serial.print(F("LIPO"));
755 | } else {
756 | Serial.print(F("standard or button cell"));
757 | }
758 | }
759 | Serial.println(" detected");
760 | #endif
761 | if ((sLIPOSupplyDetected && sVCCVoltageMillivolt < VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_LIPO)
762 | || (!sLIPOSupplyDetected
763 | && ((sBODLevelIsBelow2_7 && sVCCVoltageMillivolt < VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_STANDARD)
764 | || (!sBODLevelIsBelow2_7 && sVCCVoltageMillivolt < VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_STANDARD_BOD2_7)))
765 |
766 | ) {
767 | sVCCVoltageTooLow = true;
768 | sVCCMonitoringDelayCounter = 4; // VCC too low -> check every 2 minutes
769 | } else {
770 | sVCCVoltageTooLow = false;
771 | sVCCMonitoringDelayCounter = (VCC_MONITORING_DELAY_MIN * 60) / TEMPERATURE_SAMPLE_SECONDS; // VCC OK -> check every hour
772 | }
773 | }
774 | }
775 |
776 | /*
777 | * Watchdog wakes CPU periodically and all we have to do is call sleep_cpu();
778 | * aWatchdogPrescaler (see wdt.h) can be one of
779 | * WDTO_15MS, 30, 60, 120, 250, WDTO_500MS
780 | * WDTO_1S to WDTO_8S
781 | */
782 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler) {
783 | sleep_enable()
784 | ;
785 | set_sleep_mode(tSleepMode);
786 | MCUSR = ~_BV(WDRF); // Clear WDRF in MCUSR
787 |
788 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
789 | #define WDTCSR WDTCR
790 | #endif
791 | // Watchdog interrupt enable + reset interrupt flag -> needs ISR(WDT_vect)
792 | uint8_t tWDTCSR = _BV(WDIE) | _BV(WDIF) | (aWatchdogPrescaler & 0x08 ? _WD_PS3_MASK : 0x00) | (aWatchdogPrescaler & 0x07); // handles that the WDP3 bit is in bit 5 of the WDTCSR register,
793 | WDTCSR = _BV(WDCE) | _BV(WDE); // clear lock bit for 4 cycles by writing 1 to WDCE AND WDE
794 | WDTCSR = tWDTCSR; // set final Value
795 | }
796 |
797 | /*
798 | * This interrupt wakes up the cpu from sleep
799 | */
800 | ISR(WDT_vect) {
801 | ;
802 | }
803 |
804 | uint8_t getBODLevelFuses() {
805 | return boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) & (~FUSE_BODLEVEL2 | ~FUSE_BODLEVEL1 | ~FUSE_BODLEVEL0 );
806 | }
807 |
808 | /*
809 | * @return true if BODS flag is existent - should be true for ATtiny85 revision C and later
810 | */
811 | bool isBODSFlagExistent() {
812 | sleep_bod_disable()
813 | ;
814 | /*
815 | * Check if flag can be set - this works only for ATtini85 revision C, which is hardly seen in the wild.
816 | */
817 | return MCUCR & _BV(BODS);
818 | }
819 |
820 | #if defined(DEBUG)
821 | /*
822 | * Output description for all fuses except "DebugWire enabled"
823 | */
824 | void printFuses(void) {
825 | uint8_t tLowFuseBits = boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS);
826 | Serial.print(F("\nLowFuses="));
827 | Serial.printHex(tLowFuseBits);
828 |
829 | Serial.print(F("\nClock divide by 8 "));
830 | if (tLowFuseBits & ~FUSE_CKDIV8) {
831 | Serial.print(F("not "));
832 | }
833 | Serial.print(F("enabled\n")); // enabled is default
834 |
835 | Serial.print(F("Clock output "));
836 | if (tLowFuseBits & ~FUSE_CKOUT) {
837 | Serial.print(F("not "));
838 | }
839 | Serial.print(F("enabled\n")); // enabled is default
840 |
841 | Serial.print(F("Clock select="));
842 | uint8_t tClockSelectBits = tLowFuseBits & (~FUSE_CKSEL3 | ~FUSE_CKSEL2 | ~FUSE_CKSEL1 | ~FUSE_CKSEL0 );
843 | switch (tClockSelectBits) {
844 | case 1:
845 | Serial.print(F("16MHz PLL"));
846 | break;
847 | case 2:
848 | Serial.print(F("8MHz")); // default
849 | break;
850 | case 3:
851 | Serial.print(F("6.4MHz"));
852 | break;
853 | case 4:
854 | Serial.print(F("128kHz"));
855 | break;
856 | case 6:
857 | Serial.print(F("32.768kHz"));
858 | break;
859 | default:
860 | Serial.print(F("External"));
861 | break;
862 | }
863 |
864 | Serial.print(F("\nStart-up time="));
865 | uint8_t tStartUptimeBits = tLowFuseBits & (~FUSE_SUT1 | ~FUSE_SUT0 );
866 | if (tClockSelectBits == 1) {
867 | /*
868 | * PLL Clock has other interpretation of tStartUptimeBits
869 | */
870 | Serial.print(F("14 CK (+ 4ms)"));
871 | if (tLowFuseBits & ~FUSE_SUT0) {
872 | Serial.print(F("+ 16384 CK")); // -> 1 ms for 16 MHz clock
873 | } else {
874 | Serial.print(F(" + 1024 CK"));
875 | }
876 | if (tLowFuseBits & ~FUSE_SUT1) {
877 | Serial.print(F(" + 64ms")); // default
878 | } else {
879 | Serial.print(F(" + 4ms"));
880 | }
881 | } else {
882 | /*
883 | * Internal Calibrated RC Oscillator Clock
884 | */
885 | Serial.print(F("6 (+ 14 CK)"));
886 | switch (tStartUptimeBits) {
887 | case 0x10:
888 | Serial.print(F(" + 4ms"));
889 | break;
890 | case 0x20:
891 | Serial.print(F(" + 64ms")); // default
892 | break;
893 | default:
894 | break;
895 | }
896 | }
897 |
898 | uint8_t tHighFuseBits = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS);
899 | Serial.print(F("\n\nHighFuses="));
900 | Serial.printHex(tHighFuseBits);
901 |
902 | Serial.print(F("\nReset "));
903 | if (tHighFuseBits & ~FUSE_RSTDISBL) {
904 | Serial.print(F("not "));
905 | }
906 | Serial.print(F("disabled\n"));
907 |
908 | Serial.print(F("Serial programming "));
909 | if (tHighFuseBits & ~FUSE_SPIEN) {
910 | Serial.print(F("not "));
911 | }
912 | Serial.print(F("enabled\n"));
913 |
914 | Serial.print(F("Watchdog always on "));
915 | if (tHighFuseBits & ~FUSE_WDTON) {
916 | Serial.print(F("not "));
917 | }
918 | Serial.print(F("enabled\n"));
919 |
920 | Serial.print(F("Preserve EEPROM "));
921 | if (tHighFuseBits & ~FUSE_EESAVE) {
922 | Serial.print(F("not "));
923 | }
924 | Serial.print(F("enabled\n"));
925 |
926 | Serial.print(F("Brown-out="));
927 | uint8_t tBrownOutDetectionBits = tHighFuseBits & (~FUSE_BODLEVEL2 | ~FUSE_BODLEVEL1 | ~FUSE_BODLEVEL0 );
928 | switch (tBrownOutDetectionBits) {
929 | // 0-3 are reserved codes (for ATtiny)
930 | case 4:
931 | Serial.print(F("4.3V"));
932 | break;
933 | case 5:
934 | Serial.print(F("2.7V"));
935 | break;
936 | case 6:
937 | Serial.print(F("1.8V"));
938 | break;
939 | case 7:
940 | Serial.print(F("disabled"));
941 | break;
942 | default:
943 | break;
944 | }
945 |
946 | uint8_t tExtFuseBits = boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS);
947 | Serial.print(F("\n\nExtFuses="));
948 | Serial.printHex(tExtFuseBits);
949 | Serial.print(F("\nSelf programming "));
950 | if (tExtFuseBits & ~FUSE_SELFPRGEN) {
951 | Serial.print(F("not "));
952 | }
953 | Serial.println(F("enabled\n"));
954 | }
955 |
956 | void printBODSFlagExistence() {
957 | Serial.print(F("BODS flag "));
958 | if (!isBODSFlagExistent()) {
959 | Serial.print(F("not "));
960 | }
961 | Serial.println(F("existent"));
962 | }
963 |
964 | #endif
965 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # [OpenWindowAlarm](https://github.com/ArminJo/Arduino-OpenWindowAlarm)
4 | Place this gadget on a windowsill and you will be alarmed if you leave the window open longer than five minutes.
5 | It senses the falling temperature and thus works best in winter. It requires only 0.026 milliampere. This means one battery will last the whole winter.
6 |
7 | [](https://www.gnu.org/licenses/gpl-3.0)
8 |
9 | [](https://github.com/ArminJo/Arduino-OpenWindowAlarm/releases/latest)
10 |
11 | [](https://github.com/ArminJo/Arduino-OpenWindowAlarm/commits/master)
12 |
13 | [](https://github.com/ArminJo/Arduino-OpenWindowAlarm/actions)
14 |
15 | 
16 |
17 |
18 | [](https://stand-with-ukraine.pp.ua)
19 |
20 |
21 |
22 | Available as "OpenWindowAlarm" example of Arduino library "ATtinySerialOut"
23 |
24 | #### If you find this library useful, please give it a star.
25 |
26 | 🌎 [Google Translate](https://translate.google.com/translate?sl=en&u=https://github.com/ArminJo/Arduino-OpenWindowAlarm)
27 |
28 |
29 |
30 | ### [Driver installation ->](https://github.com/ArminJo/Arduino-OpenWindowAlarm#driver-installation)
31 |
32 | ### Sourcecode
33 | Der **Sourcecode** kann von [hier](https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/master/OpenWindowAlarm/OpenWindowAlarm.ino) kopiert werden.
34 | Das Programm ist auch als Beispiel der Arduino "ATtinySerialOut" Bibliothek - unter *Datei -> Beispiele -> Beispiele aus eigenen Bibliotheken - ATtinySerialOut -> OpenWindowAlarm* verf�gbar. Die Bibliothek kann mit *Werkzeuge -> Bibliotheken verwalten...* oder *Strg+Umschalt+I* geladen werden. Dabei "SerialOut" als Suchstring benutzen.
35 |
36 | The **sourcecode** can be copied from [here](https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/master/OpenWindowAlarm/OpenWindowAlarm.ino).
37 | The application is also available as an example of the [Arduino ATtinySerialOut library](https://github.com/ArminJo/ATtinySerialOut) - use *File -> Examples -> Examples from Custom Libraries - ATtinySerialOut -> OpenWindowAlarm*.
38 | You can load the library with *Tools -> Manage Libraries...* or *Ctrl+Shift+I*. Use "SerialOut" as filter string.
39 |
40 | | YouTube video | Instructable |
41 | |---------|------|
42 | | [](https://youtu.be/6l_QOM59nyc)
OpenWindowAlarm on a windowsill | [](https://www.instructables.com/id/Arduino-Open-Window-Detector-for-Winter/) |
43 |
44 | # Functional overview
45 | Every 24 seconds a reading is taken of the ATtiny internal temperature sensor which has a resolution of 1 degree.
46 | If temperature is lower than the "old" temperature value, an **alarm is issued five minutes later** if by then the condition still holds true.
47 | **Detection of an open window** is indicated by a longer 20 ms blink and a short click every 24 seconds.
48 | **Low battery** is indicated by beeping and flashing the LED every 24 seconds. The beep and the flash are longer than for an open window detection.
49 |
50 | # How to make your own
51 | ### The parts you need:
52 | 
53 | ### Add one of the power supplies
54 | | AAA battery case | CR2032 case | LiPo battery |
55 | |---|---|---|
56 | |  |  |  |
57 |
58 | ## Programming the Digispark board
59 | ### Installation of the core for Digispark boards for the Arduino IDE
60 | Install the Digispark board for the Arduino IDE as described in http://digistump.com/wiki/digispark/tutorials/connecting. Instead of **http://digistump.com/package_digistump_index.json** you must use **http://drazzy.com/package_drazzy.com_index.json** as Digispark board URL in Arduino *File/Preferences*. In the *Boards Manager* install the latest **ATTinyCore** version.
61 | Since we want to save power, the board clock is 1 MHz so you must choose **ATTinyCore->ATtiny85 (Micronucleus / DigiSpark)**
62 | as board in the *Tools* menu and set the clock to **1MHz (no USB)**.
63 | 
64 |
65 | ## Driver installation
66 | For Windows you must install the **Digispark driver** before you can program the board.
67 | if you have the *Digistump AVR Boards* already installed, then the driver is located in `%UserProfile%\AppData\Local\Arduino15\packages\ATTinyCore\tools\micronucleus\2.5-azd1b`. Just execute the `Install_Digistump_Drivers.bat` file.
68 | **Or** download it [here](https://github.com/digistump/DigistumpArduino/releases/download/1.6.7/Digistump.Drivers.zip), open it and run `InstallDrivers.exe`.
69 |
70 | ### German instructions
71 | Leider muss der Treiber f�r das Digispark Board manuell installiert werden. Der **Digispark Treiber** kann von [hier](https://github.com/digistump/DigistumpArduino/releases/download/1.6.7/Digistump.Drivers.zip) heruntergeladen werden. Dann die Datei �ffnen und `InstallDrivers.exe` ausf�hren.
72 | Wenn die Digispark Boards in der Arduino IDE schon installiert sind, ist der Treiber bereits auf der Platte unter `%UserProfile%\AppData\Local\Arduino15\packages\ATTinyCore\tools\micronucleus\2.5-azd1b`. Am einfachsten installiert man ihn, wenn man das Board einsteckt und wenn das unbekannte Ger�t im Ger�te-Manager auftaucht, *Treiber aktualisieren* ausw�hlt. Dann *Auf dem Computer nach Treibersoftware suchen* w�hlen, `C:\Users\` w�hlen und *Weiter* klicken.
73 | Bei der Nachfrage *M�chten sie diese Ger�tesoftware installieren* auf *installieren* klicken.
74 |
75 | Wenn das **Board nicht erkannt** wird (kein Ger�usch beim Einstecken) kann es daran liegen, dass die Buchse zu tief ist, dann eine ander Buchse oder ein USB Verl�ngerungskabel benutzen.
76 |
77 | ### Compile and upload the program to the board
78 | Install the Arduino library **ATtinySerialOut** and select the OpenWindowsAlarm example with *File -> Examples -> Examples from Custom Libraries -ATtinySerialOut -> OpenWindowAlarm*
79 | **OR**
80 | create a new sketch with *File -> New* and name it `OpenWindowAlarm` in the Arduino IDE and copy the code from [OpenWindowAlarm.ino](https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/master/OpenWindowAlarm/OpenWindowAlarm.ino).
81 |
82 | Compile and upload it. Keep in mind, that **upload will not work if the speaker is connected**.
83 | If everything works well, the built-in LED of the Board will blink 5 times (for the 5 minutes alarm delay) and then start flashing after 8 seconds with an interval of 24 seconds to signal each temperature reading.
84 |
85 | ## Power reduction
86 | Before power reduction changes
87 | 
88 |
89 | We now have a Digispark board that [consumes 6 mA at 1MHz and 3,7 volt](https://github.com/ArminJo/micronucleus-firmware#measured-digispark-pll-clock-supply-current). With a battery of **2000 mAh** it will run for **14 days**. But it is possible to reduce power consumption to **27 �A** in 3 Steps.
90 | 1. **Disabling the power LED** by breaking the copper wire that connects the power LED to the diode with a knife or removing / disabling the 102 resistor saves 2/2.2 mA.
91 | 2. **Removing the VIN voltage regulator** saves 1.5/3.0 mA.
92 | The board now needs 3/4.3 mA at 3.7/5 volt and the 2000mAh battery will last for 28 days.
93 | 3. **Disconnecting the USB D- Pullup resistor** (marked 152) from 5 volt (VCC). Disconnect it by breaking the copper wire on the side of the resistor that points to the ATtiny.
94 | **This disables the USB interface** and in turn the possibility to program the Digispark board via USB. To **enable it again**, but still save power, **connect the resistor (marked 152) directly to the USB V+** that is easily available at the outer side of the diode.
95 | The correct side of the diode can be found by using a continuity tester. One side of this diode is connected to pin 8 of the ATtiny (VCC) and Digispark 5V. The other side is connected to the USB V+.
96 |
97 | Now the USB pullup resistor is only activated if the Digispark board is connected to USB e.g. during programming.
98 | The board now consumes **27 �A** during sleep.
99 |
100 | The software loop needs 2.1 ms (plus 3 times 64 ms startup time) => active time is around 1/125 of total time.
101 | During the loop the power consumption is 100 times the sleep current => Loop adds **80%** to total power consumption.
102 | We now have an average current consumption of **75 �A** and the 2000mAh battery will last for **3 years**.
103 |
104 | The BOD current of 20 �A can only be disabled by setting fuses via ISP programmer](https://www.google.de/search?q=arduino+as+isp) and a connecting adapter. We can also reduce the start-up time from sleep from 64 to to 5 ms.
105 | For reprogramming the fuses, you can use [this script](https://github.com/ArminJo/micronucleus-firmware/blob/master/utils/Write%2085%20Fuses%20E1%20DF%20FE%20-%20Digispark%20default%20without%20BOD%20and%20Pin5%20and%20fast%20startup.cmd).
106 | Without BOD and with fast startup we have an average current consumption of **9 �A** and are still able to program the ATtiny by USB.
107 |
108 | ## Reset button
109 | **If you do not want to remove power to reset the alarm**, connect a reset button between PB5 and ground.
110 | I did this by connecting the unconnected VIN copper surface to PB5 and soldering the reset button directly to the VIN pin hole and the big ground surface of the removed VIN voltage regulator.
111 | If you want to **get rid of the 5 seconds wait** for USB connection **after reset**, you can [change the micronucleus kernel on the ATtiny85](https://github.com/ArminJo/DigistumpArduino#update-the-bootloader).
112 |
113 | ### After power reduction changes and reset button assembly
114 | | Both patches on front | Reset connection on back |
115 | |---|---|
116 | |  |  |
117 | |  |  |
118 | | |  |
119 |
120 | ## Loudspeaker disassembly
121 | | Part 1 | Part 2 |
122 | |---|---|
123 | |  |  |
124 |
125 |
126 | ## Module samples
127 | | | |
128 | |---|---|
129 | | 
Powered by 2 AAA batteries | 
Powered by CR2032 coin cell |
130 | | 
Powered by LiPo battery | 
Back viev with CR2032 coin cell |
131 | | 
With 16 Ω buzzer from an old Pc | 
Compact version |
132 |
133 | Different reset buttons and connectors
134 | 
135 |
136 | # Placement
137 | Place the board on a windowsill and connect it to the supply.
138 | If the temperature on the sill is lower than the temperature where the board was originally located, it will take additional 5 minutes to adopt to the new start value to avoid false alarm.
139 | 
140 |
141 | # Internal operation
142 | * An open window is detected after `TEMPERATURE_COMPARE_AMOUNT * TEMPERATURE_SAMPLE_SECONDS` (48) seconds of reading a temperature with a value of `TEMPERATURE_DELTA_THRESHOLD_DEGREE` (2) lower
143 | than the temperature `TEMPERATURE_COMPARE_DISTANCE * TEMPERATURE_SAMPLE_SECONDS` (192 seconds-> 3 minutes and 12 seconds) before.
144 | * The delay is implemented by sleeping 3 times at `SLEEP_MODE_PWR_DOWN` for a period of 8 seconds -the maximum hardware sleep time- to reduce power consumption.
145 |
146 | * If an **open window is detected**, this is indicated by a longer **20 ms blink** and a **short click** every 24 seconds.
147 | Therefore, the internal sensor has a time of 3 minutes to adjust to the outer temperature in order to capture even small changes in temperature.
148 | The greater the temperature change the earlier the sensor value will change and detect an open window.
149 |
150 | * `OPEN_WINDOW_ALARM_DELAY_MINUTES` (5) minutes after open window detection the **alarm is activated**.
151 | The alarm will not start or an activated alarm will stop if the current temperature is greater than the minimum measured temperature (+ 1) i.e. the window has been closed already.
152 |
153 | * The **initial alarm** lasts for 10 minutes. After this, it is activated for a period of 10 seconds with a increasing break time from 24 seconds up to 5 minutes.
154 |
155 | * At **power-on** the VCC voltage is measured used to **determine the type of battery** using `VCC_VOLTAGE_LIPO_DETECTION` (3.6 volt).
156 |
157 | * Every `VCC_MONITORING_DELAY_MIN` (60) minutes the battery voltage is measured. Depending on the detected battery type, **low battery voltage** is indicated by **beeping and flashing the LED every 24 seconds**.
158 | Only the beep (not the flash) is significantly longer than the beep for an open window detection.
159 | Low battery voltage is defined by `VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_LIPO` (3550 Millivolt) or `VCC_VOLTAGE_LOWER_LIMIT_MILLIVOLT_STANDARD` (2350 Millivolt).
160 |
161 | * After power-on, the **inactive settling time** is 5 minutes. If the board is getting colder during the settling time, 4:15 (or 8:30) minutes are added to avoid false alarms after power-on.
162 |
163 | * If you enable `DEBUG` by activating line 62, you can monitor the serial output with 115200 baud at P2 to see what is happening.
164 |
165 | # Revision History
166 | ### Version 1.3.3
167 | - 3. party libs.
168 | ### Version 1.3.2
169 | - Adapted MCUSR handling.
170 | ### Version 1.3.1
171 | - Check for closed window happens only the first 10 minutes of alarm.
172 | ### Version 1.3.0
173 | - Changed voltage low detection.
174 | - Improved DEBUG output.
175 | ### Version 1.2.2
176 | - Converted to Serial.print.
177 | - New PWMTone() without tone().
178 | ### Version 1.2.1
179 | - Fixed bug in check for temperature rising after each alarm.
180 | ### Version 1.2
181 | - Improved sleep, detecting closed window also after start of alarm, reset behavior.
182 | - Changed LIPO detection threshold.
183 | - Fixed analog reference bug.
184 |
185 | ### Sample TRACE output after reset
186 | ```
187 | Changed OSCCAL from 0x52 to 0x4e
188 | START ../src/OpenWindowAlarm.cpp
189 | Version 1.2.1 from Nov 5 2019
190 | Alarm delay = 5 minutes
191 | MCUSR=0x2 LFuse=0x225 WDTCR=0x0 OSCCAL=0x78
192 | Booting from reset
193 | VCC=4022mV - LIPO detected
194 | Temp=300 Old=0 New=300
195 | Temp=298 Old=0 New=598
196 | Temp=298 Old=0 New=596
197 | Temp=297 Old=0 New=595
198 | Temp=296 Old=0 New=593
199 | Temp=296 Old=0 New=592
200 | Temp=296 Old=0 New=592
201 | Temp=296 Old=0 New=592
202 | Temp=296 Old=0 New=592
203 | Temp=296 Old=0 New=592
204 | Temp=296 Old=300 New=592
205 | Detected porting to a colder place -> reset
206 | Temp=296 Old=0 New=296
207 | Temp=296 Old=0 New=592
208 | ...
209 | Temp=296 Old=0 New=592
210 | Temp=296 Old=296 New=592
211 | Temp=296 Old=592 New=592
212 | Temp=296 Old=592 New=592
213 | ```
214 | #### If you find this program useful, please give it a star.
215 |
--------------------------------------------------------------------------------
/SetPath.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | @echo Looking for Arduino installation at default paths
3 | @set ARDUINO_DIRECTORY="C:\Program Files (x86)\Arduino"
4 | @if exist %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe goto setPath
5 | @echo avr-gcc.exe not found in directory %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe
6 | @set ARDUINO_DIRECTORY="C:\Program Files\Arduino IDE"
7 | @if exist %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe goto setPath
8 | @echo avr-gcc.exe not found in directory %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe
9 | @set ARDUINO_DIRECTORY="%localappdata%Program Files\Arduino IDE"
10 | @if exist %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe goto setPath
11 | @echo avr-gcc.exe not found in directory %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe
12 |
13 | @set ARDUINO_DIRECTORY=E:\Elektro\arduino
14 | @echo Looking for Arduino installation at user defined directory %ARDUINO_DIRECTORY%
15 | @if exist %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe goto setPath
16 | @echo ERROR - avr-gcc.exe not found in directory %ARDUINO_DIRECTORY%\hardware\tools\avr\bin\avr-gcc.exe
17 | @pause
18 | @exit
19 |
20 | :setPath
21 | @echo Add Arduino binaries, Digispark launcher and our windows make.exe directory to path
22 | @set PATH=%ARDUINO_DIRECTORY%\hardware\tools\avr\bin;%UserProfile%\AppData\Local\Arduino15\packages\digistump\tools\micronucleus\2.0a4;..\windows_exe;%PATH%
23 |
--------------------------------------------------------------------------------
/extras/Arduino-Open-Window-Detector-for-Winter.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/extras/Arduino-Open-Window-Detector-for-Winter.pdf
--------------------------------------------------------------------------------
/pictures/4Modules.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/4Modules.jpg
--------------------------------------------------------------------------------
/pictures/ArduinoDigisparkBoardSelection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/ArduinoDigisparkBoardSelection.png
--------------------------------------------------------------------------------
/pictures/BatteryCase.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/BatteryCase.jpg
--------------------------------------------------------------------------------
/pictures/BoardSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/BoardSettings.png
--------------------------------------------------------------------------------
/pictures/CR2032Back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/CR2032Back.jpg
--------------------------------------------------------------------------------
/pictures/CR2032Front.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/CR2032Front.jpg
--------------------------------------------------------------------------------
/pictures/CR2032Holder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/CR2032Holder.jpg
--------------------------------------------------------------------------------
/pictures/CR3032.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/CR3032.jpg
--------------------------------------------------------------------------------
/pictures/Digispark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Digispark.jpg
--------------------------------------------------------------------------------
/pictures/Final-Version-Compact.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-Version-Compact.jpg
--------------------------------------------------------------------------------
/pictures/Final-Version-Detail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-Version-Detail.jpg
--------------------------------------------------------------------------------
/pictures/Final-Version-Detail1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-Version-Detail1.jpg
--------------------------------------------------------------------------------
/pictures/Final-Version-Detail_annotated.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-Version-Detail_annotated.jpg
--------------------------------------------------------------------------------
/pictures/Final-VersionAAA.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-VersionAAA.jpg
--------------------------------------------------------------------------------
/pictures/Final-VersionLiPo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Final-VersionLiPo.jpg
--------------------------------------------------------------------------------
/pictures/LiPo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/LiPo.jpg
--------------------------------------------------------------------------------
/pictures/Loudspeaker1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Loudspeaker1.jpg
--------------------------------------------------------------------------------
/pictures/Loudspeaker2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Loudspeaker2.jpg
--------------------------------------------------------------------------------
/pictures/MiniUSBModule.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/MiniUSBModule.jpg
--------------------------------------------------------------------------------
/pictures/OpenWindowAlarm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/OpenWindowAlarm.png
--------------------------------------------------------------------------------
/pictures/OpenWindowAlarm1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/OpenWindowAlarm1.jpg
--------------------------------------------------------------------------------
/pictures/OpenWindowAlarm2AAA.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/OpenWindowAlarm2AAA.jpg
--------------------------------------------------------------------------------
/pictures/OpenWindowAlarmLiPo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/OpenWindowAlarmLiPo.jpg
--------------------------------------------------------------------------------
/pictures/OpenWindowAlarmLiPo_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/OpenWindowAlarmLiPo_2.jpg
--------------------------------------------------------------------------------
/pictures/Parts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Parts.jpg
--------------------------------------------------------------------------------
/pictures/PartsAll_annotated.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/PartsAll_annotated.jpg
--------------------------------------------------------------------------------
/pictures/Patch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Patch.jpg
--------------------------------------------------------------------------------
/pictures/Patch1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/Patch1.jpg
--------------------------------------------------------------------------------
/pictures/PatchAnnotated.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/PatchAnnotated.jpg
--------------------------------------------------------------------------------
/pictures/PatchWithResetOnBack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/PatchWithResetOnBack.jpg
--------------------------------------------------------------------------------
/pictures/ResetConnectionBack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/ResetConnectionBack.jpg
--------------------------------------------------------------------------------
/pictures/instructables-logo-v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArminJo/Arduino-OpenWindowAlarm/7d8a2228c0239aaf29ddac0f6cfdd831d27f959d/pictures/instructables-logo-v2.png
--------------------------------------------------------------------------------
/pictures/readme.txt:
--------------------------------------------------------------------------------
1 | This folder contains only the pictures of the project.
2 |
--------------------------------------------------------------------------------