Беспроводный Lighting-Sensor с питанием от CR2450

Сделать какой-нибудь беспроводный датчик, содержащий барометр, термометр, гигрометр или все в одном флаконе, с питанием от 220В — это не проблема. А вот запитать такое устройство от батареек или аккумуляторов уже интереснее. Ну а если это будет дисковая литиевая батарейка (таблетка) — вообще здорово, потому как устройство получится весьма компактным. 3ecdb0e32dff4633b5451cced4effca8.jpg
Единственным препятствием для использования «таблетки» является ее небольшая емкость. Но и среди «таблеток» есть вполне подходящие экземпляры, например CR2450, с заявленной емкостью 550-610 мАч.
Поскольку на момент изготовления устройства у меня уже был готовый комнатный и уличный датчики температуры и влажности, то я решил сделать Lighting-sensor на основе BH1750 и разместить его на балконе, дополнительно снабдив его датчиком температуры DS18B20.
Так как у меня все окна выходят на юг, то уличный датчик температуры и влажности подвержен значительному влиянию солнечных лучей, данное влияние приходится компенсировать за счет погодных данных получаемых из интернета, в дальнейшем, я планирую использовать информацию об уровне освещения для расчета компенсации.
Для максимального снижения потребляемой датчиком энергии было решено:
1. Отказаться от использования готовой Ардуинки и использовать непосредственно микроконтроллер ATmega 328P-PU с кварцем на 8МГц,. От использования внутреннего RC- генератора я отказался, т.к. из-за значительных перепадов внешней температуры, рабочая частота получается не стабильной.
2. Использовать библиотеку LowPower и задействовать для контроллера режим энергосбережения LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF)
3. Датчик DS18B20 запитать от цифрового вывода микроконтроллера и включать его непосредственно перед замером.
4. Радиомодуль и BH1750 запитать напрямую, использовав при этом, режимы энергосбережения, radio.powerDown() и Light.configure(BH1750_ONE_TIME_HIGH_RES_MODE) соответственно.
Вооружившись программатором USBasp,
5903763aee93423b9d5de8b5ca44dabc.jpg
прошил в контроллер фьюзы ну и конечно же загрузчик. Собрал прототип, набросал тестовый скетч и замерил потребляемые токи: в режиме сна получилось 14мкА, в режиме снятия показаний с датчиков — 3-4мА, в режиме передачи данных через nRF24 на метеостанцию — 16-18мА. С учетом того, что снимать показания с датчиков решил 1 раз в 3 минуты, а передавать раз в 10 минут — сенсор получается весьма экономичным.
Для изготовления конечного устройства использовал кусочек макетной платы и провод МГТФ. В результате вот что получилось:
1d7c4fe1624c42ad9132a49decca339e.jpg
Для контроля за состоянием батарейки используем встроенные возможности ATmega, хорошо описанные в этой статье.
Процесс прошивки загрузчика и фьюзов подробно освещен тути тут.
Общая идея создания автономного сенсора позаимствована на сайте maniacbug.

Датчик уже второй месяц лежит (и работает) на балконе на подоконнике, исправно отправляет данные. Контроль питания сообщает о том, что за это время напряжение источника снизилось с 3,05V при первом включении до 2,98V. Однако днем, когда солнце прогревает балкон, напряжение может подняться вплоть до 3,00V. Жду холодов, посмотрим как покажет себя выбранная батарейка при отрицательных температурах.
При выбранной продолжительности режима sleep, получается что сенсор бодрствует всего 492-495 сек за 24 часа.
Устройство в сборе, но к сожалению пока без корпуса, не могу найти подходящий
8af06a2266d549c6acc7e9533fa105dc.jpg
07db1d1e1cfc40e4972a9db2197b0519.jpg

Исходный код
#include <LowPower.h>
#include <SPI.h>
#include <RF24Network.h>   
#include <RF24.h>          
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <BH1750.h>
/////// Сlass AVG //////////////////////////////////////////////////////////
class AVG {
  #define DEPTH_AVG 10
  private :
     int depth;
     long *mas;
     int cur;
     boolean first;
  public :
     boolean fDebug;  

  AVG(int d)
  {
    if( d == 0 || d > DEPTH_AVG)depth = DEPTH_AVG;
    else depth = d;
    mas = (long *)malloc(sizeof(long)*depth);
    first = true;
    cur   = 0;
    fDebug = false;
  }
  void Set(long x)
  {
    if( first )
    {
       for( int i=0; i<depth; i++ )mas[i] = x;
       cur = 0;
    }
    else
    {
    mas[cur++] = x;
    if( cur >= depth ) cur = 0; 
    }
    first = false;  
  }
  long Get()
  {
    long x=0;
    for( int i=0; i<depth; i++ )
          {
            x+=mas[i];
          }
    x/=depth;
    return(x);
  }
};
/////////////////////////////////////////////////////////////////////
#define pinPowerSensor 4    //Питание сенсора подключаем к Pin 4
#define ONE_WIRE_BUS 3
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
BH1750 Light;
RF24 radio(/*ce*/ 9, /*cs*/ 10);
RF24Network network(radio);
// Адрес нашего узла
uint16_t this_node = 02;
// Адрес, куда по умолчанию сливаем показания
uint16_t main_node = 0;
// Номер канала
const int Chanel = 11;
int ReadCount;
int iSend     = 3;    // Интервал отправки показаний 
int WaitCount = 24;   // Количество циклов сна по 8 сек.
bool allowSend = false; //признак разрешения на отправку
AVG vAVG(3);
AVG tAVG(3);
AVG lAVG(3);
struct StreetMeteoSensor
{
  char    ID[5];     //ИД устойства
  unsigned long UpTime;
  int   T;           //Температура
  long  L;           //Освещенность  (Lx) 
  int   V;           //уровень заряда батарейки (mV)
};
struct StreetMeteoSensor sensor;
RF24NetworkHeader header(main_node, 'I');
////////////////////////////////////////////
#define DEBUG_MODE     0
////////////////////////////////////////////
void setup(void)
{
    if (DEBUG_MODE) 
  {
    Serial.begin(115200);
    //tAVG.fDebug = true;  
    //lAVG.fDebug = true;      
    Serial.println("-- START --");  
    WaitCount = 5;
  }
  for (int i = 1; i < 15; i++)  
  {
    pinMode(i, OUTPUT);
    digitalWrite(i,LOW);
  }
  digitalWrite(pinPowerSensor,HIGH);
  char ID[5] = "LTS1";
  memcpy(sensor.ID, ID, 5);  
  ReadCount = iSend;
  SPI.begin();
  delay(500);
  radio.begin();
  network.begin( Chanel, this_node );  
  sensors.begin();  
  Light.begin(BH1750_ONE_TIME_HIGH_RES_MODE);
  delay(500);
  radio.powerDown();    
  digitalWrite(pinPowerSensor,LOW);  
  Light.configure(BH1750_POWER_DOWN);
  if (DEBUG_MODE)  Serial.println("-- End SETUP --"); 
}

void loop(void)
{
  //считываем значения датчиков
 if (DEBUG_MODE) Serial.println("data read...");
  // Для BH1750 используем собственный режим энергосбережения, 
  Light.configure(BH1750_POWER_ON);                    // Включаем сенсор
  Light.configure(BH1750_ONE_TIME_HIGH_RES_MODE);     // Устанавливаем режим измерений
  digitalWrite(pinPowerSensor,HIGH);                  // Подаем питание на DS18B20
  sensors.setResolution(TEMP_9_BIT);   
  delay(250); 
  sensors.requestTemperatures();
  int T = sensors.getTempCByIndex(0);
  digitalWrite(pinPowerSensor,LOW);  // Отключаем питанеи DS18B20
  long L = Light.readLightLevel();  //Режим ONE_TIME автоматически отключает питание
  int V = readVcc();  
  /////////////////////////////////////////////////////////////
  int tt = tAVG.Get();
  int vv = vAVG.Get();
  long ll = lAVG.Get();
  if (L > 0 || ll > 0 || T < tt-2 || T > tt+2 || V < vv-100/*0.1V*/) 
  { // уменьшим кол-во передач в темное время
    tAVG.Set(T);
    lAVG.Set(L);
    vAVG.Set(V); 
    allowSend = true;     //Есть что отправить - Взведем флаг
    // Если освещенность изменилась с 0 (рассвет)
    if (ll == 0 &&  L > 0)
      lAVG.Set(L);  
  }
  ReadCount++;        
  /////////////////////////////////////////////////////////////
  if (DEBUG_MODE)
  {
    Serial.print("T= ");
    Serial.print(tAVG.Get());
    Serial.print(": ");
    Serial.print(T);    
    Serial.print("; L= ");
    Serial.print(lAVG.Get());
    Serial.print(": ");    
    Serial.print(L);    
    Serial.print("; V= ");
    Serial.print(vAVG.Get());
    Serial.print(": ");    
    Serial.print(V);        
    Serial.print("; ReadCount= ");    
    Serial.println(ReadCount);
  }  
  /////////////////////////////////////////////////////////////
  // считаем количество выполненных замеров
  if ( ReadCount >= iSend && allowSend )
  {
    // Отправка данных на базовый узел
    ReadCount = 0;
    allowSend = false;   
    radio.powerUp();
    delay(50);
    sensor.T = tAVG.Get();  
    sensor.L = lAVG.Get();
    sensor.V = vAVG.Get();    
    sensor.UpTime = millis()/1000;     
    network.write(header,&sensor,sizeof(sensor));
    radio.powerDown();  
  }    
  sleep();
}
// Переводим устройство в режим низкого энергопотребления
void sleep()
{
  if (DEBUG_MODE) {Serial.println("----- SLEEP -----");Serial.flush();}
  for (int i = 0; i < WaitCount; i++)  //впадаем в спячку 
  {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); 
  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Измерение напряжения питания
int readVcc() 
{
  int result;
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(75); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  result = ADCL;
  result |= ADCH<<8;
  if (DEBUG_MODE) {Serial.print("result=");Serial.println(result);}
  result =  1125300L / result; // (Kvcc * 1023.0 * 1000) (in mV);
  return result; 
}


© Geektimes