123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /*
- * 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 <henrik.ekblad@mysensors.org>
- * Copyright (C) 2013-2018 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"
- #include "avr/boot.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)
- #define INVALID_INTERRUPT_NUM (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.
-
- 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);
- }
-
- void 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;
- }
- }
- }
- }
-
- 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;
- if (ms > 0u) {
- // sleep for defined time
- hwInternalSleep(ms);
- } else {
- // sleep until ext interrupt triggered
- hwPowerDown(WDTO_SLEEP_FOREVER);
- }
- if (interruptWakeUp()) {
- ret = static_cast<int8_t>(_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);
- }
-
- if (ms > 0u) {
- // sleep for defined time
- 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<int8_t>(_wokeUpByInterrupt);
- }
- // Clear woke-up-by-interrupt flag, so next sleeps won't return immediately.
- _wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
-
- return ret;
- }
-
- 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) = boot_signature_byte_get(0x00);
- *((uint8_t *)uniqueID + 1) = boot_signature_byte_get(0x02);
- *((uint8_t *)uniqueID + 2) = boot_signature_byte_get(0x04);
- *((uint8_t *)uniqueID + 3) = boot_signature_byte_get(0x01); //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 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();
- // restore timer settings
- TCCR1A = TCCR1Asave;
- TCCR1C = TCCR1Csave;
- // return frequency in 1/10MHz (accuracy +- 10%)
- return TCNT1 * 2048UL / 100000UL;
- }
-
- 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<int8_t>((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);
- }
-
- void hwDebugPrint(const char *fmt, ... )
- {
- #ifndef MY_DISABLED_SERIAL
- char fmtBuffer[MY_SERIAL_OUTPUT_SIZE];
- #ifdef MY_GATEWAY_SERIAL
- // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
- snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%" PRIu8 ";0;%" PRIu8 ";%" PRIu32 " "),
- C_INTERNAL, I_LOG_MESSAGE, hwMillis());
- MY_DEBUGDEVICE.print(fmtBuffer);
- #else
- // prepend timestamp
- MY_DEBUGDEVICE.print(hwMillis());
- MY_DEBUGDEVICE.print(F(" "));
- #endif
- va_list args;
- va_start(args, fmt);
- vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
- #ifdef MY_GATEWAY_SERIAL
- // Truncate message if this is gateway node
- fmtBuffer[sizeof(fmtBuffer) - 2] = '\n';
- fmtBuffer[sizeof(fmtBuffer) - 1] = '\0';
- #endif
- va_end(args);
- MY_DEBUGDEVICE.print(fmtBuffer);
- MY_DEBUGDEVICE.flush();
- #else
- (void)fmt;
- #endif
- }
|