mirror of
https://github.com/mysensors/MySensors.git
synced 2026-03-23 00:16:58 +01:00
433 lines
13 KiB
C++
433 lines
13 KiB
C++
/*
|
|
* 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-2026 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 "MyHwMegaAVR.h"
|
|
|
|
void hwRtcInit(void)
|
|
{
|
|
// Initialize RTC to 1kHz internal oscillator
|
|
while (RTC.STATUS > 0) { } /* Wait for all register to be synchronized */
|
|
|
|
RTC.CTRLA = RTC_PRESCALER_DIV32768_gc /* 32768 */
|
|
| 0 << RTC_RTCEN_bp /* Enable: disabled */
|
|
| 0 << RTC_RUNSTDBY_bp; /* Run In Standby: disabled */
|
|
|
|
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; /* 32KHz divided by 32 */
|
|
}
|
|
|
|
bool hwInit(void)
|
|
{
|
|
hwRtcInit();
|
|
#if !defined(MY_DISABLED_SERIAL)
|
|
MY_SERIALDEVICE.begin(MY_BAUD_RATE);
|
|
#if defined(MY_GATEWAY_SERIAL)
|
|
while (!MY_SERIALDEVICE) {}
|
|
#endif
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
#define PIT_SLEEP_FOREVER 0
|
|
|
|
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)
|
|
{
|
|
// TODO: Check do I need that
|
|
//EIFR = _BV(interrupt);
|
|
}
|
|
|
|
void pit_reset()
|
|
{
|
|
RTC.PITCTRLA = 0;
|
|
RTC.PITINTCTRL = 0;
|
|
}
|
|
|
|
void hwPitRtcInit(uint8_t cycles)
|
|
{
|
|
while (RTC.PITSTATUS > 0) { }/* Wait for all register to be synchronized */
|
|
|
|
/*
|
|
RTC_PERIOD_CYC16_gc = (0x03<<3), /* RTC Clock Cycles 16
|
|
RTC_PERIOD_CYC32_gc = (0x04<<3), /* RTC Clock Cycles 32
|
|
RTC_PERIOD_CYC64_gc = (0x05<<3), /* RTC Clock Cycles 64
|
|
RTC_PERIOD_CYC128_gc = (0x06<<3), /* RTC Clock Cycles 128
|
|
RTC_PERIOD_CYC256_gc = (0x07<<3), /* RTC Clock Cycles 256
|
|
RTC_PERIOD_CYC512_gc = (0x08<<3), /* RTC Clock Cycles 512
|
|
RTC_PERIOD_CYC1024_gc = (0x09<<3), /* RTC Clock Cycles 1024
|
|
RTC_PERIOD_CYC2048_gc = (0x0A<<3), /* RTC Clock Cycles 2048
|
|
RTC_PERIOD_CYC4096_gc = (0x0B<<3), /* RTC Clock Cycles 4096
|
|
RTC_PERIOD_CYC8192_gc = (0x0C<<3), /* RTC Clock Cycles 8192
|
|
RTC_PERIOD_CYC16384_gc = (0x0D<<3), /* RTC Clock Cycles 16384
|
|
RTC_PERIOD_CYC32768_gc = (0x0E<<3), /* RTC Clock Cycles 32768
|
|
*/
|
|
RTC.PITCTRLA = cycles
|
|
| 1 << RTC_PITEN_bp; /* Enable: enabled */
|
|
|
|
RTC.PITINTCTRL = 1 << RTC_PI_bp; /* Periodic Interrupt: enabled */
|
|
}
|
|
|
|
uint8_t getSleepingPeriod(uint32_t ms)
|
|
{
|
|
for (uint8_t period = 14u; period > 2; --period) {
|
|
const uint16_t comparatorMS = (32768 >> (15 - period));
|
|
if (ms >= comparatorMS) {
|
|
return period;
|
|
}
|
|
}
|
|
|
|
return 3; // 16ms with 1kHz internal oscillator
|
|
}
|
|
|
|
uint32_t hwPowerDown(uint32_t ms)
|
|
{
|
|
// Let serial prints finish (debug, log etc)
|
|
#ifndef MY_DISABLED_SERIAL
|
|
MY_SERIALDEVICE.flush();
|
|
#endif
|
|
if (_beforeSleep) {
|
|
_beforeSleep();
|
|
}
|
|
ADC0.CTRLA &= ~ADC_ENABLE_bm; // ADC off
|
|
|
|
if (ms != PIT_SLEEP_FOREVER) {
|
|
|
|
// 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) {
|
|
uint8_t period = getSleepingPeriod(ms);
|
|
const uint16_t comparatorMS = 32768 >> (15 - period);
|
|
|
|
cli();
|
|
hwPitRtcInit(period << 3);
|
|
sei();
|
|
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
sleep_enable();
|
|
sleep_cpu();
|
|
sleep_disable();
|
|
|
|
cli();
|
|
RTC.PITCTRLA = 0; /* RTC Clock Cycles Off, Enable: disabled */
|
|
sei();
|
|
|
|
ms -= comparatorMS;
|
|
}
|
|
} else {
|
|
// Eternal sleep
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
sleep_enable();
|
|
sleep_cpu();
|
|
sleep_disable();
|
|
|
|
sei();
|
|
}
|
|
|
|
ADC0.CTRLA |= ADC_ENABLE_bm; // ADC on
|
|
|
|
if (_afterSleep) {
|
|
_afterSleep();
|
|
}
|
|
|
|
if (interruptWakeUp()) {
|
|
return ms;
|
|
}
|
|
return 0ul;
|
|
}
|
|
|
|
ISR (RTC_PIT_vect)
|
|
{
|
|
RTC.PITINTFLAGS = 1; // Clear interrupt flag
|
|
}
|
|
|
|
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 = hwPowerDown(ms);
|
|
} else {
|
|
// sleep until ext interrupt triggered
|
|
hwPowerDown(PIT_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);
|
|
}
|
|
|
|
sleepRemainingMs = 0ul;
|
|
if (ms > 0u) {
|
|
// sleep for defined time
|
|
sleepRemainingMs = hwPowerDown(ms);
|
|
} else {
|
|
// sleep until ext interrupt triggered
|
|
hwPowerDown(PIT_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;
|
|
}
|
|
|
|
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) = SIGROW.DEVICEID0;
|
|
*((uint8_t *)uniqueID + 4) = SIGROW.DEVICEID1;
|
|
*((uint8_t *)uniqueID + 5) = SIGROW.DEVICEID2;
|
|
|
|
// SIGROW.SERNUM[9:0].
|
|
for(uint8_t idx = 0; idx < 10; idx++) {
|
|
*((uint8_t *)uniqueID + 6 + idx) = *(&SIGROW.SERNUM0 + idx);
|
|
}
|
|
|
|
|
|
return true; // unique ID returned
|
|
}
|
|
|
|
uint16_t hwCPUVoltage(void)
|
|
{
|
|
analogReference(INTERNAL1V024);
|
|
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
analogRead(ADC_VDDDIV10);
|
|
}
|
|
|
|
uint16_t adc = analogRead(ADC_VDDDIV10);
|
|
uint16_t voltage = adc * (uint16_t) 10.24; // value in mV
|
|
|
|
return voltage; // value in mV
|
|
}
|
|
|
|
uint16_t hwCPUFrequency(void)
|
|
{
|
|
#ifdef F_CPU
|
|
return F_CPU / 100000UL;
|
|
#else
|
|
cli();
|
|
const uint8_t rtcPitCtrlA = RTC.PITCTRLA;
|
|
const uint8_t rtcPitIntCtrl = RTC.PITINTCTRL;
|
|
const uint8_t rtcPitPitIntFlags = RTC.PITINTFLAGS;
|
|
|
|
const uint8_t tcaEvCtrl = TCA0.SINGLE.EVCTRL;
|
|
const uint8_t tcaCtrlA = TCA0.SINGLE.CTRLA;
|
|
|
|
|
|
// setup timer1
|
|
TCA0.SINGLE.CNT = 0x0; /* Count: 0x0 */
|
|
|
|
TCA0.SINGLE.EVCTRL = 0 << TCA_SINGLE_CNTAEI_bp /* Count on Event Input A: disabled */
|
|
| 0 << TCA_SINGLE_CNTBEI_bp /* Count on Event Input B: disabled */
|
|
| TCA_SINGLE_EVACTA_CNT_POSEDGE_gc /* Count on positive edge event */
|
|
| TCA_SINGLE_EVACTB_UPDOWN_gc; /* Count on prescaled clock. Event controls count direction.
|
|
Up-count when event line is 0, down-count when event line
|
|
is 1. */
|
|
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc /* System Clock */
|
|
| 1 << TCA_SINGLE_ENABLE_bp /* Module Enable: enabled */
|
|
| 0 << TCA_SINGLE_RUNSTDBY_bp; /* Run Standby: disabled */
|
|
|
|
// set pit
|
|
while (RTC.PITSTATUS >
|
|
0) {} /* Wait for all register to be synchronized */
|
|
RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc /* RTC Clock Cycles 16384 - 500ms */
|
|
| 1 << RTC_PITEN_bp; /* Enable: enabled */
|
|
RTC.PITINTCTRL = 1 << RTC_PI_bp; /* Periodic Interrupt: enabled */
|
|
|
|
// wait for pit to interrupt
|
|
while (bit_is_clear(RTC.PITINTFLAGS, RTC_PI_bp)) {};
|
|
|
|
TCA0.SINGLE.CTRLA = 0; /* Enable: disabled */
|
|
|
|
RTC.PITCTRLA &= ~(1 << RTC_PITEN_bp); /* Enable: disabled */
|
|
|
|
sei();
|
|
|
|
const uint16_t result = (TCA0.SINGLE.CNT * 2048UL) / 100000UL;
|
|
|
|
// Reset timers
|
|
RTC.PITCTRLA = rtcPitCtrlA;
|
|
RTC.PITINTCTRL = rtcPitIntCtrl;
|
|
RTC.PITINTFLAGS = rtcPitPitIntFlags;
|
|
|
|
TCA0.SINGLE.EVCTRL = tcaEvCtrl;
|
|
TCA0.SINGLE.CTRLA = tcaCtrlA;
|
|
|
|
// return frequency in 1/10MHz (accuracy +- 10%)
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
int8_t hwCPUTemperature(void)
|
|
{
|
|
analogReference(INTERNAL1V024);
|
|
uint16_t adc_reading = analogRead(ADC_TEMPERATURE);
|
|
|
|
int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed offset from signature row
|
|
uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned gain/slope from signature row
|
|
|
|
uint32_t temp = adc_reading - sigrow_offset;
|
|
temp *= sigrow_gain; // Result might overflow 16-bit variable (10-bit + 8-bit)
|
|
temp += 0x80; // Add 256/2 to get correct integer rounding on division below
|
|
temp >>= 8; // Divide result by 256 to get processed temperature in Kelvin
|
|
uint16_t temperature_in_K = temp;
|
|
|
|
return temperature_in_K - 273.15; // value in Celsius
|
|
}
|
|
|
|
uint16_t hwFreeMem(void)
|
|
{
|
|
extern int __heap_start, *__brkval;
|
|
int v;
|
|
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
|
|
}
|