24 сентября 2017 г.

Метеостанция на arduino #7. Датчик температуры и влажности DHT22

Доставили мне датчик DHT22 (за 13! дней с момента заказа, ехал из Латвии). Измерил потребление: 3 мкА в неактивном режиме и импульсно до 1,4 мА во время опроса. Для коммутации повесил питание датчика на цифровой пин D6. Информационный вывод подключил к D7. "Поковырял" библиотеку DHT-sensor-library, вытянул только то, что мне нужно, немного переписав. На 1 МГц пока не получилось опросить датчик (надо прошаривать код библиотеки - функции dht22_read и dht22_expectPulse см. ниже, а также даташит к DHT22), поэтому пришлось вернуться к 16 МГц (что привело к увеличению энергопотребления, но только в активном режиме). 
Схема:
Код:
//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// для ЖК дисплея 1602 с I2C
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

// кнопка
#define BUTTON_PIN        2

// подключение ЖК дисплея 1602 I2C
// LCD 1602 I2C <---> Arduino
// GND <------------> GND
// VCC <------------> D4
// LCD SDA <--------> A4
// LCD SCL <--------> A5
// A <--------------> D5
#define LCD_POWER_PIN       4
#define LCD_BACKLIGHT_PIN   5
#define LCD_BACKLIGHT_VALUE 50

// подключение датчика температуры/влажности DHT22
// DHT22 <---> Arduino
// VCC <------------> D6
// DATA <-----------> D7
#define DHT22_POWER_PIN     6
#define DHT22_DATA_PIN      7

// устанавливаем адрес дисплея
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// переменные для храниения "адреса" датчика DHT22 (порт/пин)
uint8_t dht22_port;
uint8_t dht22_bit;

uint32_t dht22_maxcycles;  

// в этот массив будем принимать данные от датчика DHT22
uint8_t data[5];

// переменные для хранения текущих и предыдущих значений температуры и влажности
float currentTemperature = 0;
int8_t currentHumidity = 0;
float previousTemperature = 0;
int8_t previousHumidity = 0;

// символ градуса
uint8_t symbolDegree[8] = {
  B11100,
  B10100,
  B11100,
  B00000,
  B00111,
  B01000,
  B01000,
  B00111
};

// символ процента
uint8_t symbolPercent[8] = {
  B11000,
  B11001,
  B00010,
  B00100,
  B00100,
  B01000,
  B10011,
  B00011
};

// да, да, получилось только через переменные вывести символы :)
int symbolDegreeNumber = 0;
int symbolPercentNumber = 1;

void setup() {
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A3; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }

  // установливаем на пине с кнопкой подтяжку к VCC
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  digitalWrite(BUTTON_PIN, HIGH);
  
  // устанавливаем обработчик прерывания INT0
  attachInterrupt(0, m_wake_up, FALLING);

  // инициализируем датчик DHT22
  dht22_init(DHT22_DATA_PIN);
}

void loop() {
  // засыпаем
  m_sleep();

  if(dht22_read()) {
          
    currentTemperature = dht22_getTemperature();
    currentHumidity = round(dht22_getHumidity());

    dht22_off();
    
    lcd_show(false);
    
    previousTemperature = currentTemperature;
    previousHumidity = currentHumidity;
  }
  else {
    lcd_show(true);
  } 
}

// функция для погружения МК в сон
void m_sleep() {
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    // на второй ноге висит прерывание, поэтому ее не трогаем
    if (i == 2) continue;
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }
  
  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию
  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим
  sleep_enable();

  // разрешаем прерывания
  sei();
  
  // собственно засыпаем
  sleep_cpu();              
}

// обработчик прерывания по нажатию кнопки
void m_wake_up () {
  // запрещаем прерывания
  cli();
  
  // запрещаем режим сна
  sleep_disable();

  // включаем периферию
  power_all_enable();
}

///////////////////////////////////////////////////////////////////////////////
// LCD 1602 I2C

void lcd_on() {
  digitalWrite(LCD_POWER_PIN, HIGH);
  analogWrite(LCD_BACKLIGHT_PIN, LCD_BACKLIGHT_VALUE);
}

void lcd_off() {
  digitalWrite(LCD_POWER_PIN, LOW);
  digitalWrite(LCD_BACKLIGHT_PIN, LOW);
}

void lcd_show(bool error) {
  lcd_on();
  
  // инициализируем дисплей
  lcd.begin(16,2);

  // создаем символы градуса и процента
  lcd.createChar(symbolDegreeNumber, symbolDegree);
  lcd.createChar(symbolPercentNumber, symbolPercent);
    
  if (error) {
    lcd.setCursor(0,0);
    lcd.print("Error DHT");  
  }
  else {
    lcd.setCursor(0,0);
    lcd.print(currentTemperature, 1);
    lcd.setCursor(4,0);
    lcd.write(symbolDegreeNumber);
  
    lcd.setCursor(7,0);
    lcd.print(currentHumidity, 1);
    lcd.setCursor(9,0);
    lcd.write(symbolPercentNumber);
  
    if (previousTemperature != 0) {   
      float deltaT = currentTemperature - previousTemperature;
      lcd.setCursor(0,1);
      lcd.print(deltaT, 1);
  
      int8_t deltaH = currentHumidity - previousHumidity;
      lcd.setCursor(7,1);
      lcd.print(deltaH);
    }
  }

  _delay_ms(2000);
  lcd_off();
}

///////////////////////////////////////////////////////////////////////////////
// DHT22

void dht22_init(uint8_t pin) {
  #ifdef __AVR
    dht22_bit = digitalPinToBitMask(pin);
    dht22_port = digitalPinToPort(pin);
  #endif
  dht22_maxcycles = microsecondsToClockCycles(1000);  // 1 millisecond timeout for
                                                 // reading pulses from DHT sensor.
  // Note that count is now ignored as the DHT reading algorithm adjusts itself
  // basd on the speed of the processor.

  pinMode(pin, INPUT_PULLUP);  
}

void dht22_on() {
  digitalWrite(DHT22_POWER_PIN, HIGH);
}

void dht22_off() {
  digitalWrite(DHT22_POWER_PIN, LOW);
}

boolean dht22_read() {
  dht22_on();

  // немного подождем, чтобы датчик "пришел в себя" :)
  _delay_ms(500);
  
  // Reset 40 bits of received data to zero.
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;

  // Send start signal.  See DHT datasheet for full signal diagram:
  //   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf

  // Go into high impedence state to let pull-up raise data line level and
  // start the reading process.
  digitalWrite(DHT22_DATA_PIN, HIGH);
  _delay_ms(250);

  // First set data line low for 20 milliseconds.
  pinMode(DHT22_DATA_PIN, OUTPUT);
  digitalWrite(DHT22_DATA_PIN, LOW);
  _delay_ms(20);

  uint32_t cycles[80];
  {
    cli();

    // End the start signal by setting data line high for 40 microseconds.
    digitalWrite(DHT22_DATA_PIN, HIGH);
    _delay_us(40);

    // Now start reading the data line to get the value from the DHT sensor.
    pinMode(DHT22_DATA_PIN, INPUT_PULLUP);
    _delay_us(10);  // Delay a bit to let sensor pull data line low.

    // First expect a low signal for ~80 microseconds followed by a high signal
    // for ~80 microseconds again.
    if (dht22_expectPulse(LOW) == 0) {
      sei();
      dht22_off();
      return false;
    }
    if (dht22_expectPulse(HIGH) == 0) {
      sei();
      dht22_off();
      return false;
    }

    // Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
    // microsecond low pulse followed by a variable length high pulse.  If the
    // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
    // then it's a 1.  We measure the cycle count of the initial 50us low pulse
    // and use that to compare to the cycle count of the high pulse to determine
    // if the bit is a 0 (high state cycle count < low state cycle count), or a
    // 1 (high state cycle count > low state cycle count). Note that for speed all
    // the pulses are read into a array and then examined in a later step.
    for (int i=0; i<80; i+=2) {
      cycles[i]   = dht22_expectPulse(LOW);
      cycles[i+1] = dht22_expectPulse(HIGH);
    }
  } // Timing critical code is now complete.

  // Inspect pulses and determine which ones are 0 (high state cycle count < low
  // state cycle count), or 1 (high state cycle count > low state cycle count).
  for (int i=0; i<40; ++i) {
    uint32_t lowCycles  = cycles[2*i];
    uint32_t highCycles = cycles[2*i+1];
    if ((lowCycles == 0) || (highCycles == 0)) {
      sei();
      dht22_off();
      return false;
    }
    data[i/8] <<= 1;
    // Now compare the low and high cycle times to see if the bit is a 0 or 1.
    if (highCycles > lowCycles) {
      // High cycles are greater than 50us low cycle count, must be a 1.
      data[i/8] |= 1;
    }
    // Else high cycles are less than (or equal to, a weird case) the 50us low
    // cycle count so this must be a zero.  Nothing needs to be changed in the
    // stored data.
  }

  // Check we read 40 bits and that the checksum matches.
  if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
    sei();
    dht22_off();
    return true;
  }
  else {
    sei();
    dht22_off();
    return false;
  }
}

float dht22_getTemperature() {
  float t;

  t = data[2] & 0x7F;
  t *= 256;
  t += data[3];
  t *= 0.1;
  if (data[2] & 0x80) {
    t *= -1;
  }
  
  return t;
}

float dht22_getHumidity() {
  float h;
  
  h = data[0];
  h *= 256;
  h += data[1];
  h *= 0.1;

  return h;
}

uint32_t dht22_expectPulse(bool level) {
  uint32_t count = 0;
  
  // On AVR platforms use direct GPIO port access as it's much faster and better
  // for catching pulses that are 10's of microseconds in length:
  #ifdef __AVR
    uint8_t portState = level ? dht22_bit : 0;
    while ((*portInputRegister(dht22_port) & dht22_bit) == portState) {
      if (count++ >= dht22_maxcycles) {
        return 0; // Exceeded timeout, fail.
      }
    }
  // Otherwise fall back to using digitalRead (this seems to be necessary on ESP8266
  // right now, perhaps bugs in direct port access functions?).
  #else
    while (digitalRead(DHT22_DATA_PIN) == level) {
      if (count++ >= dht22_maxcycles) {
        return 0; // Exceeded timeout, fail.
      }
    }
  #endif

  return count;
}
//код--
Или здесь.

Подключил все это дело к одному аккуму 18650 (аккум одолжил). Измерил энергопотребление девайса: 7 мкА - режим сна, 12,6 мА - отображение показаний датчика при 4,2 В напряжения питания.
Фото:

В нижней строке отображается разница в показаниях между текущим и предыдущим замерами. Пока как-то так. Буду ждать датчик давления.

Комментариев нет: