1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
|
/*
Requires the use of https://github.com/damellis/attiny for the ATtiny85 rather than
https://github.com/SpenceKonde/ATTinyCore since ATTinyCore takes up too much memory.
Démontre le fonctionnement du TX avec un ATtiny85 en utilisant 2 broches pour la radio.
* L'ATtiny s'allume, prend les mesures du capteur, envoie les données, puis s'éteint.
* La consommation de courant est d'environ 5 microampères lorsqu'il est éteint.
* La minuterie chien de garde est utilisée pour réveiller l'ATtiny à un intervalle configurable.
* Le récepteur peut modifier certains paramètres en fournissant un accusé de réception du paquet NewSettingsPacket.
l'exemple 'Sensor_RX' pour plus de détails. Les nouveaux paramètres sont enregistrés dans l'eeprom.
* Des messages d'information sont envoyés au récepteur pour certains événements, comme le chargement de l'eeprom.
Cette logique peut être utilisée pour le débogage à distance, ce qui est très cool.
* La broche physique 5 (Arduino 0) agit comme un interrupteur d'alimentation. La radio et les capteurs ont tous un VCC connecté,
et la broche physique 5 fournit leur connexion à GND. Ainsi, lorsque l'ATtiny sort de son sommeil, il fait de la broche physique 5 une OUTPUT et la connecte à GND.
la broche physique 5 une SORTIE et la met à l'état BAS afin de mettre sous tension la radio et les capteurs.
* Suivez le 2-Pin Hookup Guide sur https://github.com/dparson55/NRFLite pour créer les connexions MOMI et SCK
pour la radio.
* Il y a une image du circuit dans le même dossier que ce fichier .ino.
Connexions ATtiny
* Broche physique 1 > Bouton de réinitialisation > GND
* Broche physique 2 > Connexion entre la résistance 10K et la thermistance
* Broche physique 3 > MOMI du circuit radio à 2 broches
* Broche physique 4 > GND
* Broche physique 5 > Connexion au GND de la radio et au GND des circuits de la thermistance, de la LED et de la LDR
* Broche physique 6 > SCK du circuit radio à 2 broches
* Broche physique 7 > Connexion entre la LDR et la résistance de 1K
* Broche physique 8 > VCC (pas plus de 3,6 volts puisque c'est le maximum pour la radio)
*/
#include "NRFLite.h"
#include <avr/eeprom.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
const static uint8_t PIN_RADIO_MOMI = 4;
const static uint8_t PIN_RADIO_SCK = 1;
const static uint8_t PIN_LDR = A1;
const static uint8_t PIN_THERM = A3;
const static uint8_t PIN_POWER_BUS = 0;
const static uint8_t DESTINATION_RADIO_ID = 0;
const static uint16_t THERM_BCOEFFICIENT = 4050;
const static uint8_t THERM_NOMIAL_TEMP = 25; // Thermistor nominal temperature in C.
const static uint16_t THERM_NOMINAL_RESISTANCE = 10000; // Thermistor resistance at the nominal temperature.
const static uint16_t THERM_SERIES_RESISTOR = 10000; // Value of resistor in series with the thermistor.
const uint8_t EEPROM_SETTINGS_VERSION = 1;
struct EepromSettings
{
uint8_t RadioId;
uint32_t SleepIntervalSeconds;
float TemperatureCorrection;
uint8_t TemperatureType;
float VoltageCorrection;
uint8_t Version;
};
struct RadioPacket
{
uint8_t FromRadioId;
uint32_t FailedTxCount;
uint16_t Brightness;
float Temperature;
uint8_t TemperatureType; // 0 = Celsius, 1 = Fahrenheit
float Voltage;
};
struct MessagePacket
{
uint8_t FromRadioId;
uint8_t message[31]; // Can hold a 30 character string + the null terminator.
};
enum ChangeType
{
ChangeRadioId,
ChangeSleepInterval,
ChangeTemperatureCorrection,
ChangeTemperatureType,
ChangeVoltageCorrection
};
struct NewSettingsPacket
{
ChangeType ChangeType;
uint8_t ForRadioId;
uint8_t NewRadioId;
uint32_t NewSleepIntervalSeconds;
float NewTemperatureCorrection;
uint8_t NewTemperatureType;
float NewVoltageCorrection;
};
NRFLite _radio;
EepromSettings _settings;
uint32_t _failedTxCount;
#define eepromBegin() eeprom_busy_wait(); noInterrupts() // Details on https://youtu.be/_yOcKwu7mQA
#define eepromEnd() interrupts()
void processSettingsChange(NewSettingsPacket newSettings); // Need to pre-declare this function since it uses a custom struct as a parameter (or use a .h file instead).
void setup()
{
// Enable the power bus. This provides power to all the sensors and radio, and we'll turn it off when we sleep to conserve power.
pinMode(PIN_POWER_BUS, OUTPUT);
digitalWrite(PIN_POWER_BUS, LOW);
// Load settings from eeprom.
eepromBegin();
eeprom_read_block(&_settings, 0, sizeof(_settings));
eepromEnd();
if (_settings.Version == EEPROM_SETTINGS_VERSION)
{
setupRadio();
sendMessage(F("Loaded settings")); // Save memory using F() for strings. Details on https://learn.adafruit.com/memories-of-an-arduino/optimizing-sram
}
else
{
// The settings version in eeprom is not what we expect, so assign default values.
_settings.RadioId = 1;
_settings.SleepIntervalSeconds = 2;
_settings.TemperatureCorrection = 0;
_settings.TemperatureType = 0;
_settings.VoltageCorrection = 0;
_settings.Version = EEPROM_SETTINGS_VERSION;
setupRadio();
sendMessage(F("Eeprom old, using defaults"));
saveSettings();
}
}
void setupRadio()
{
if (!_radio.initTwoPin(_settings.RadioId, PIN_RADIO_MOMI, PIN_RADIO_SCK, NRFLite::BITRATE250KBPS))
{
while (1); // Cannot communicate with the radio so stop all processing.
}
}
void loop()
{
// Put the microcontroller, sensors, and radio into a low power state. Processing stops here until the watchdog timer wakes us up.
sleep(_settings.SleepIntervalSeconds);
// Now that we are awake, collect all the sensor readings.
RadioPacket radioData;
radioData.FromRadioId = _settings.RadioId;
radioData.FailedTxCount = _failedTxCount;
radioData.Brightness = analogRead(PIN_LDR);
radioData.Temperature = getTemperature();
radioData.TemperatureType = _settings.TemperatureType;
radioData.Voltage = getVcc();
// Try sending the sensor data.
if (_radio.send(DESTINATION_RADIO_ID, &radioData, sizeof(radioData)))
{
// If we are here it means the data was sent successful.
// Check to see if the receiver provided an acknowledgement data packet.
// It will do this if it wants us to change one of our settings.
if (_radio.hasAckData())
{
NewSettingsPacket newSettingsData;
_radio.readData(&newSettingsData);
// Confirm the settings we received are meant for us (maybe it was trying to change the settings for a different transmitter).
if (newSettingsData.ForRadioId == _settings.RadioId)
{
processSettingsChange(newSettingsData);
}
else
{
sendMessage(F("Ignoring settings change"));
String msg = F(" request for Radio ");
msg += String(newSettingsData.ForRadioId);
sendMessage(msg);
sendMessage(F(" Please try again"));
}
}
}
else
{
_failedTxCount++;
}
}
ISR(WDT_vect) // Watchdog interrupt handler.
{
wdt_disable();
}
float getTemperature()
{
// Details on https://learn.adafruit.com/thermistor
float thermReading = analogRead(PIN_THERM);
float steinhart = 1023 / thermReading - 1;
steinhart = THERM_SERIES_RESISTOR / steinhart;
steinhart /= THERM_NOMINAL_RESISTANCE;
steinhart = log(steinhart);
steinhart /= THERM_BCOEFFICIENT;
steinhart += 1.0 / (THERM_NOMIAL_TEMP + 273.15);
steinhart = 1.0 / steinhart;
steinhart -= 273.15; // Convert to C
if (_settings.TemperatureType == 1)
{
steinhart = (steinhart * 9.0) / 5.0 + 32.0; // Convert to F
}
return steinhart + _settings.TemperatureCorrection;
}
float getVcc()
{
// Details on http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
ADMUX = _BV(MUX3) | _BV(MUX2); // Select internal 1.1V reference for measurement.
delay(2); // Let voltage stabilize.
ADCSRA |= _BV(ADSC); // Start measuring.
while (ADCSRA & _BV(ADSC)); // Wait for measurement to complete.
uint16_t adcReading = ADC;
float vcc = 1.1 * 1024.0 / adcReading; // Note the 1.1V reference can be off +/- 10%, so calibration is needed.
return vcc + _settings.VoltageCorrection;
}
void processSettingsChange(NewSettingsPacket newSettings)
{
String msg;
if (newSettings.ChangeType == ChangeRadioId)
{
msg = F("Changing Id to ");
msg += newSettings.NewRadioId;
sendMessage(msg);
_settings.RadioId = newSettings.NewRadioId;
setupRadio();
}
else if (newSettings.ChangeType == ChangeSleepInterval)
{
sendMessage(F("Changing sleep interval"));
_settings.SleepIntervalSeconds = newSettings.NewSleepIntervalSeconds;
}
else if (newSettings.ChangeType == ChangeTemperatureCorrection)
{
sendMessage(F("Changing temperature"));
sendMessage(F(" correction"));
_settings.TemperatureCorrection = newSettings.NewTemperatureCorrection;
}
else if (newSettings.ChangeType == ChangeTemperatureType)
{
sendMessage(F("Changing temperature type"));
_settings.TemperatureType = newSettings.NewTemperatureType;
}
else if (newSettings.ChangeType == ChangeVoltageCorrection)
{
sendMessage(F("Changing voltage correction"));
_settings.VoltageCorrection = newSettings.NewVoltageCorrection;
}
saveSettings();
}
void saveSettings()
{
EepromSettings settingsCurrentlyInEeprom;
eepromBegin();
eeprom_read_block(&settingsCurrentlyInEeprom, 0, sizeof(settingsCurrentlyInEeprom));
eepromEnd();
// Do not waste 1 of the 100,000 guaranteed eeprom writes if settings have not changed.
if (memcmp(&settingsCurrentlyInEeprom, &_settings, sizeof(_settings)) == 0)
{
sendMessage(F("Skipped eeprom save, no change"));
}
else
{
sendMessage(F("Saving settings"));
eepromBegin();
eeprom_write_block(&_settings, 0, sizeof(_settings));
eepromEnd();
}
}
void sendMessage(String msg)
{
MessagePacket messageData;
messageData.FromRadioId = _settings.RadioId;
// Ensure the message is not too large for the MessagePacket.
if (msg.length() > sizeof(messageData.message) - 1)
{
msg = msg.substring(0, sizeof(messageData.message) - 1);
}
// Convert the message string into an array of bytes.
msg.getBytes((unsigned char*)messageData.message, msg.length() + 1);
_radio.send(DESTINATION_RADIO_ID, &messageData, sizeof(messageData));
}
void sleep(uint32_t seconds)
{
uint32_t totalPowerDownSeconds = 0;
uint8_t canSleep8Seconds;
_radio.powerDown(); // Put the radio into a low power state.
pinMode(PIN_POWER_BUS, INPUT); // Disconnect power bus.
ADCSRA &= ~_BV(ADEN); // Disable ADC to save power.
while (totalPowerDownSeconds < seconds)
{
canSleep8Seconds = seconds - totalPowerDownSeconds >= 8;
if (canSleep8Seconds)
{
WDTCR = _BV(WDIE) | _BV(WDP3) | _BV(WDP0); // Enable watchdog and set 8 second interrupt time.
totalPowerDownSeconds += 8;
}
else
{
WDTCR = _BV(WDIE) | _BV(WDP2) | _BV(WDP1); // 1 second interrupt time.
totalPowerDownSeconds++;
}
wdt_reset();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
//MCUCR |= _BV(BODS) | _BV(BODSE); // Disable brown-out detection (only supported on ATtiny85 RevC and higher).
//MCUCR = _BV(BODS);
sleep_cpu();
sleep_disable();
}
ADCSRA |= _BV(ADEN); // Re-enable ADC.
pinMode(PIN_POWER_BUS, OUTPUT); // Re-enable power bus. It will already be LOW since it was configured in 'setup()'.
setupRadio(); // Re-initialize the radio.
} |
Partager