/* * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each * repeater and gateway builds a routing tables in EEPROM which keeps track of the * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad * Copyright (C) 2013-2019 Sensnology AB * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors * * Documentation: http://www.mysensors.org * Support Forum: http://forum.mysensors.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. */ #include "MyHwAVR.h" bool hwInit(void) { #if !defined(MY_DISABLED_SERIAL) MY_SERIALDEVICE.begin(MY_BAUD_RATE); #if defined(MY_GATEWAY_SERIAL) while (!MY_SERIALDEVICE) {} #endif #endif return true; } #define WDTO_SLEEP_FOREVER (0xFFu) volatile uint8_t _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu. volatile uint8_t _wakeUp1Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback. volatile uint8_t _wakeUp2Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback. static uint32_t sleepRemainingMs = 0ul; void wakeUp1(void) { // Disable sleep. When an interrupt occurs after attachInterrupt, // but before sleeping the CPU would not wake up. // Ref: http://playground.arduino.cc/Learning/ArduinoSleepCode sleep_disable(); detachInterrupt(_wakeUp1Interrupt); // First interrupt occurred will be reported only if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt) { _wokeUpByInterrupt = _wakeUp1Interrupt; } } void wakeUp2(void) { sleep_disable(); detachInterrupt(_wakeUp2Interrupt); // First interrupt occurred will be reported only if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt) { _wokeUpByInterrupt = _wakeUp2Interrupt; } } inline bool interruptWakeUp(void) { return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM; } void clearPendingInterrupt(const uint8_t interrupt) { EIFR = _BV(interrupt); } // Watchdog Timer interrupt service routine. This routine is required // to allow automatic WDIF and WDIE bit clearance in hardware. ISR (WDT_vect) { } void hwPowerDown(const uint8_t wdto) { // Let serial prints finish (debug, log etc) #ifndef MY_DISABLED_SERIAL MY_SERIALDEVICE.flush(); #endif // disable ADC for power saving ADCSRA &= ~(1 << ADEN); // save WDT settings const uint8_t WDTsave = WDTCSR; if (wdto != WDTO_SLEEP_FOREVER) { wdt_enable(wdto); // enable WDT interrupt before system reset WDTCSR |= (1 << WDCE) | (1 << WDIE); } else { // if sleeping forever, disable WDT wdt_disable(); } set_sleep_mode(SLEEP_MODE_PWR_DOWN); cli(); sleep_enable(); #if defined(__AVR_ATmega328P__) sleep_bod_disable(); #endif // Enable interrupts & sleep until WDT or ext. interrupt sei(); // Directly sleep CPU, to prevent race conditions! // Ref: chapter 7.7 of ATMega328P datasheet sleep_cpu(); sleep_disable(); // restore previous WDT settings cli(); wdt_reset(); // enable WDT changes WDTCSR |= (1 << WDCE) | (1 << WDE); // restore saved WDT settings WDTCSR = WDTsave; sei(); // enable ADC ADCSRA |= (1 << ADEN); } uint32_t hwInternalSleep(uint32_t ms) { // Sleeping with watchdog only supports multiples of 16ms. // Round up to next multiple of 16ms, to assure we sleep at least the // requested amount of time. Sleep of 0ms will not sleep at all! ms += 15u; while (!interruptWakeUp() && ms >= 16) { for (uint8_t period = 9u; ; --period) { const uint16_t comparatorMS = 1 << (period + 4); if ( ms >= comparatorMS) { hwPowerDown(period); // 8192ms => 9, 16ms => 0 ms -= comparatorMS; break; } } } if (interruptWakeUp()) { return ms; } return 0ul; } int8_t hwSleep(uint32_t ms) { // Return what woke the mcu. // Default: no interrupt triggered, timer wake up int8_t ret = MY_WAKE_UP_BY_TIMER; sleepRemainingMs = 0ul; if (ms > 0u) { // sleep for defined time sleepRemainingMs = hwInternalSleep(ms); } else { // sleep until ext interrupt triggered hwPowerDown(WDTO_SLEEP_FOREVER); } if (interruptWakeUp()) { ret = static_cast(_wokeUpByInterrupt); } // Clear woke-up-by-interrupt flag, so next sleeps won't return immediately. _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; return ret; } int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms) { return hwSleep(interrupt, mode, INVALID_INTERRUPT_NUM, 0u, ms); } int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, uint32_t ms) { // ATMega328P supports following modes to wake from sleep: LOW, CHANGE, RISING, FALLING // Datasheet states only LOW can be used with INT0/1 to wake from sleep, which is incorrect. // Ref: http://gammon.com.au/interrupts // Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt() // and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled! cli(); // attach interrupts _wakeUp1Interrupt = interrupt1; _wakeUp2Interrupt = interrupt2; // Attach external interrupt handlers, and clear any pending interrupt flag // to prevent waking immediately again. // Ref: https://forum.arduino.cc/index.php?topic=59217.0 if (interrupt1 != INVALID_INTERRUPT_NUM) { clearPendingInterrupt(interrupt1); attachInterrupt(interrupt1, wakeUp1, mode1); } if (interrupt2 != INVALID_INTERRUPT_NUM) { clearPendingInterrupt(interrupt2); attachInterrupt(interrupt2, wakeUp2, mode2); } sleepRemainingMs = 0ul; if (ms > 0u) { // sleep for defined time sleepRemainingMs = hwInternalSleep(ms); } else { // sleep until ext interrupt triggered hwPowerDown(WDTO_SLEEP_FOREVER); } // Assure any interrupts attached, will get detached when they did not occur. if (interrupt1 != INVALID_INTERRUPT_NUM) { detachInterrupt(interrupt1); } if (interrupt2 != INVALID_INTERRUPT_NUM) { detachInterrupt(interrupt2); } // Return what woke the mcu. // Default: no interrupt triggered, timer wake up int8_t ret = MY_WAKE_UP_BY_TIMER; if (interruptWakeUp()) { ret = static_cast(_wokeUpByInterrupt); } // Clear woke-up-by-interrupt flag, so next sleeps won't return immediately. _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; return ret; } uint32_t hwGetSleepRemaining(void) { return sleepRemainingMs; } inline void hwRandomNumberInit(void) { // This function initializes the random number generator with a seed // of 32 bits. This method is good enough to earn FIPS 140-2 conform // random data. This should reach to generate 32 Bit for randomSeed(). uint32_t seed = 0; uint32_t timeout = millis() + 20; // Trigger floating effect of an unconnected pin pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT_PULLUP); pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT); delay(10); // Generate 32 bits of datas for (uint8_t i=0; i<32; i++) { const int pinValue = analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN); // Wait until the analog value has changed while ((pinValue == analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN)) && (timeout>=millis())) { seed ^= (millis() << i); // Check of data generation is slow if (timeout<=millis()) { // Trigger pin again pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT_PULLUP); pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT); // Pause a short while delay(seed % 10); timeout = millis() + 20; } } } randomSeed(seed); } bool hwUniqueID(unique_id_t *uniqueID) { // padding (void)memset(uniqueID, MY_HWID_PADDING_BYTE, sizeof(unique_id_t)); // no unique ID for non-PB AVR, use HW specifics for diversification *((uint8_t *)uniqueID) = SIGNATURE_2; *((uint8_t *)uniqueID + 1) = SIGNATURE_1; *((uint8_t *)uniqueID + 2) = SIGNATURE_0; *((uint8_t *)uniqueID + 3) = OSCCAL; #if defined(__AVR_ATmega328PB__) // ATMEGA328PB specifics, has unique ID for(uint8_t idx = 0; idx < 10; idx++) { *((uint8_t *)uniqueID + 4 + idx) = boot_signature_byte_get(0xE + idx); } return true; // unique ID returned #else return false; // no unique ID returned #endif } uint16_t hwCPUVoltage(void) { // Measure Vcc against 1.1V Vref #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 // Vref settle delay(70); // Do conversion ADCSRA |= _BV(ADSC); while (bit_is_set(ADCSRA,ADSC)) {}; // return Vcc in mV return (1125300UL) / ADC; } uint16_t hwCPUFrequency(void) { cli(); // save WDT & timer settings const uint8_t WDTsave = WDTCSR; const uint8_t TCCR1Asave = TCCR1A; const uint8_t TCCR1Bsave = TCCR1B; const uint8_t TCCR1Csave = TCCR1C; // setup timer1 TIFR1 = 0xFF; TCNT1 = 0; TCCR1A = 0; TCCR1C = 0; // set wdt wdt_enable(WDTO_500MS); // enable WDT interrupt mode => first timeout WDIF, 2nd timeout reset WDTCSR |= (1 << WDIE); wdt_reset(); // start timer1 with 1024 prescaling TCCR1B = _BV(CS12) | _BV(CS10); // wait until wdt interrupt while (bit_is_clear(WDTCSR,WDIF)) {}; // stop timer TCCR1B = 0; // restore WDT settings wdt_reset(); WDTCSR |= (1 << WDCE) | (1 << WDE); WDTCSR = WDTsave; sei(); const uint16_t result = TCNT1 * 2048UL / 100000UL; // restore timer settings TCCR1A = TCCR1Asave; TCCR1B = TCCR1Bsave; TCCR1C = TCCR1Csave; // return frequency in 1/10MHz (accuracy +- 10%) return result; } int8_t hwCPUTemperature(void) { #if defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328BP__) || defined(__AVR_ATmega32U4__) // Set the internal reference and mux. ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3)); ADCSRA |= _BV(ADEN); // enable the ADC delay(20); // wait for voltages to become stable. ADCSRA |= _BV(ADSC); // Start the ADC // Wait until conversion done while (bit_is_set(ADCSRA, ADSC)); // temperature is in degrees Celsius return static_cast((ADCW - MY_AVR_TEMPERATURE_OFFSET) / MY_AVR_TEMPERATURE_GAIN); #else return -127; // not available #endif } uint16_t hwFreeMem(void) { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }