Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
I need monitor humidity of a room, and the humidity recorder product is so expensive, that's why I made it, I have another Android App connect with the device, you can get humidity data from cell phone and show chart on it.
get download Android App from here:
https://play.google.com/store/apps/details?id=eyes.blue.humidityrecorder
Library:
DS3231: https://github.com/NorthernWidget/DS3231
SHT31: https://github.com/adafruit/Adafruit_SHT31
4 Digit LED https://github.com/Seeed-Studio/Grove_4Digital_Display
ZEeprom https://github.com/zoubworldArduino/ZEeprom
Streaming https://www.arduinolibraries.info/libraries/streaming
HOW HARDWARE PIN CONNECT With Arduino:
SHT-31 Arduino
SCL ------- A5
SDA ------- A6
4 Digit Display
CLK ------- A1
DIO ------- A0
DS3231
SCL ------- A5
SDA ------- A6
SQW ------- D2(INT0)
HC-05 (Software serial baud rate 38400 to Arduino)
TXD ------- D4
RXD ------- D5(connect resistance if needed).
State ------- D3(INT1)
Debug:
1. If your 4Digit LED no display or display wrong number, check the RTC or INT/SQW pin of RTC, the device blink the colon of 4 Digit LED per second, it no blink if RTC not work or INT/SQW work not correct.
2. The LED indicator(LED_BUILTIN Pin on Arduino nano) show status:
WORK FINE: ON 0.5 second per 3 second.
NO RTC: Blink 1 time shortly.
NO EEPROM: Blink 2 times shortly.
NO DHT31: Blink 3 time shortly.
Humidity Recoder
Arduino#include <Arduino.h>
#include <EEPROM.h>
#include <TM1637.h>
#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <DS3231.h>
#include <ZEeprom.h>
#include <SoftwareSerial.h>
#include <Streaming.h>
#include <avr/sleep.h>
#include <LowPower.h>
//byte runtime[4] = {0, 0, 0, 0};
#define RT_BT 1;
#define RT_SYS 0;
// Runtime bluetooth
#define IsConnected 0
//#define IsVerified 1
// Settings address in ROM
#define ADDR_BRIGHTNESS 1023
#define ADDR_REC_INTERVAL 1022
#define RTC_ADDRESS 0x68
#define CLOCK_INT_PIN 2
#define YEAR 5
#define MON 4
#define DATE 3
#define HOUR 2
#define MIN 1
#define SEC 0
DS3231 rtc;
RTClib rtcLib; // For get now();
uint8_t sysClock[6] = {0, 0, 0, 0, 0, 0};
volatile bool clockIntr = false, bluetoothIntr = false;
uint32_t upTimer = 0;
#define CLOCK_WAKEUP 1
#define WDT_WAKEUP 2
byte wakeupPhase = 0;
unsigned long clockSecMillis = 0;
#define EEPROM_ADDRESS 0x57
ZEeprom * extRom;
unsigned int extRomSize = 4096;
uint8_t innerRomStartAddr = 0x00;
uint16_t innerRomEndAddr = 999;
#define CLK A1 //Pins for TM1637
#define DIO A0
TM1637 tm1637(CLK, DIO);
int clockLedBright = BRIGHT_TYPICAL;
#define DHT31_ADDRESS 0x44
Adafruit_SHT31 sht31 = Adafruit_SHT31();
bool isRecHumdt = true;
bool isRecTemp = false;
uint8_t recInterval = 1; // minute.
struct RecordSet {
uint32_t lastRecTime;
uint16_t lastRecAddr;
} ThRec;
#define BLUETOOTH_STATE_PIN 3
bool isBluetoothConnected = false;
//SoftwareSerial BTserial(4, 5);
SoftwareSerial BTserial(17, 16);
#define DUMP 1
#define SET_TIME 2
#define GET_STAT 3
long dumpIndex = -1;
byte data[32];
#define READY 0
#define BUSY 1
#define ERR 2
#define NO_RTC 1
#define NO_EEPROM 2
#define NO_DHT31 3
class Led {
byte ledPin = LED_BUILTIN;
byte sysState = READY;
unsigned long ledPrevMillis = 0;
bool hasErr=false; // hasErr is hightest priority flag, if it is true, skip sysState flag.
public:
Led() {}
Led(int pin) {
ledPin = pin;
}
void setup() {
pinMode(ledPin, OUTPUT);
}
setState(int state) {
sysState = state;
}
int getState() {
return sysState;
}
void loop() {
if(hasErr) // If hasErr is true, skip sysState flag.
ledErr();
else if (sysState == READY)
ledReady();
}
void busy(){
digitalWrite(ledPin, HIGH );
sysState = BUSY;
}
void unBusy(){
digitalWrite(ledPin, LOW );
sysState = READY;
}
void hasError(bool isErr){
hasErr=isErr;
}
void loopErrSignal(int num){
while(true){
for(int i=0;i<num;i++){
digitalWrite(ledPin, HIGH );
delay(100);
digitalWrite(ledPin, LOW );
delay(300);
}
delay(1000);
}
}
private:
void ledReady() {
if (sysClock[SEC] % 3 == 0) {
if(wakeupPhase == WDT_WAKEUP)
digitalWrite(ledPin, LOW );
else
digitalWrite(ledPin, HIGH );
}
}
void ledErr() {
if(wakeupPhase == WDT_WAKEUP)
digitalWrite(ledPin, LOW );
else
digitalWrite(ledPin, HIGH );
}
};
Led led;
void setup() {
Serial.begin(57600);
Wire.setClock(400000);
Wire.begin();
if (isDevExist(RTC_ADDRESS))
Serial.println("FOUND RTC!");
else {
Serial.println("RTC NOT found!");
led.loopErrSignal(NO_RTC);
}
// Check is EEPROM exist or not.
if (isDevExist(EEPROM_ADDRESS))
Serial.println("FOUND Eeprom!");
else {
Serial.println("Eeprom NOT found!");
led.loopErrSignal(NO_EEPROM);
}
if (sht31.begin(DHT31_ADDRESS))// Set to 0x45 for alternate i2c addr
Serial.println("FOUND SHT31!");
else {
Serial.println("Couldn't find SHT31.");
led.loopErrSignal(NO_DHT31);
}
extRom = new ZEeprom();
extRom->begin(Wire, EEPROM_ADDRESS, AT24C32);
rtc.setClockMode(false);
syncRTC();
Serial << "Get RTC time: " << sysClock[YEAR] << ", " << sysClock[MON] << ", " << sysClock[DATE] << ", " << sysClock[HOUR] << ", " << sysClock[MIN] << ", " << sysClock[SEC] << ", " << endl;
rtc.enableOscillator(true, false, 0); // Turn on
rtc.enable32kHz(true);
pinMode(CLOCK_INT_PIN, INPUT_PULLUP);
attachInterrupt(0, clockIsr, FALLING); //assign int0
BTserial.begin(38400);
attachInterrupt(1, bluetoothIsr, CHANGE); //assign int0
recInterval = EEPROM.read(ADDR_REC_INTERVAL);
if (recInterval == 0xFF) recInterval = 1;
ThRec.lastRecTime = 0;
ThRec.lastRecAddr = 0;
tm1637.init();
int bright = EEPROM.read(ADDR_BRIGHTNESS);
if (bright < 8) {
Serial.print("Get bright from option ROM: ");
Serial.println(bright);
clockLedBright = bright;
tm1637.set(bright);
}
else tm1637.set(clockLedBright);
showClockLed();
}
void clockIsr() {
clockIntr = true;
//Serial.println("Clock interrupt!");
}
void bluetoothIsr() {
bluetoothIntr = true;
//Serial.println("Bluetooth interrupt!");
}
void loop() {
//Serial.print("Program alive: ");
if (clockIntr) maintainClock();
if (bluetoothIntr) {
byte pin = digitalRead(BLUETOOTH_STATE_PIN);
if (pin == HIGH) {
isBluetoothConnected = true;
//Serial.println("Bluetooth Connected(state HIGH).");
}
else {
isBluetoothConnected = false;
//Serial.println("Bluetooth Disconnected(state LOW).");
}
bluetoothIntr = false;
}
led.loop();
showClockLed();
if (sysClock[SEC] == 0) {
// Avoid record many times in one second.
if (getUnixtimeFromSysClock() >= ThRec.lastRecTime + (recInterval * 10)) {
recHumidity();
}
}
checkBtCmd();
if (!isBluetoothConnected) {
if (wakeupPhase == CLOCK_WAKEUP) {
//Serial.println("Sleep 500 ms.");
LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
wakeupPhase = WDT_WAKEUP;
}
else {
//Serial.println("Sleep Forever");
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
}
}
void maintainClock() {
clockIntr = false;
upTimer++;
wakeupPhase = CLOCK_WAKEUP;
clockSecMillis = millis();
//Serial << "Clock Sec Millis = " << clockSecMillis << endl;
sysClock[SEC]++;
if (sysClock[SEC] < 60)
return;
sysClock[SEC] = 0;
sysClock[MIN]++;
if (sysClock[MIN] < 60)
return;
sysClock[MIN] = 0;
sysClock[HOUR]++;
if (sysClock[HOUR] == 24)
syncRTC();
//Serial.print("Got a interrupt from clock at ");
//Serial.println(millis());
}
void recHumidity() {
float t = sht31.readTemperature();
float h = sht31.readHumidity();
if (! isnan(t)) { // check if 'is not a number'
//Serial.print("Temp *C = "); Serial.println(t);
t += 0.5;
} else {
//Serial.println("Failed to read temperature");
t = -1;
}
if (! isnan(h)) { // check if 'is not a number'
//Serial.print("Hum. % = "); Serial.println(h);
h += 0.5;
} else {
//Serial.println("Failed to read humidity");
h = -1;
}
//Serial.println();
byte bt = (byte)t;
byte bh = (byte)h;
uint16_t innerRomSize = innerRomEndAddr - innerRomStartAddr + 1;
byte dataLen = (isRecTemp) ? 2 : 1;
//Serial << "Save data T: " << bt << ", H: " << bh << " to Addr ";
if (ThRec.lastRecTime == 0 || ThRec.lastRecAddr + dataLen >= extRomSize + innerRomSize) { // if lastRecTime == 0 mean it is first time to record data.
ThRec.lastRecAddr = 0;
//Serial << ThRec.lastRecAddr << "(External ROM)" << endl;
if (isRecHumdt)writeRom(ThRec.lastRecAddr, &bh, 0, 1);
if (isRecTemp)writeRom(ThRec.lastRecAddr + 1, &bt, 0, 1);
} else if (ThRec.lastRecAddr + dataLen >= extRomSize) {
// Write in inner ROM
ThRec.lastRecAddr += dataLen;
int innerAddr = ThRec.lastRecAddr - extRomSize + innerRomStartAddr;
//Serial << ThRec.lastRecAddr << "(Inner: " << innerAddr << ")" << endl;
if (isRecHumdt)EEPROM.write(innerAddr, bh);
if (isRecTemp)EEPROM.write(innerAddr + 1, bt);
} else {
// Write in external ROM
ThRec.lastRecAddr += dataLen;
//Serial << ThRec.lastRecAddr << "(External ROM)" << endl;
if (isRecHumdt)writeRom(ThRec.lastRecAddr + 1, &bh, 0, 1);
if (isRecTemp) writeRom(ThRec.lastRecAddr, &bt, 0, 1);
}
//DateTime dt = rtcLib.now();
//ThRec.lastRecTime=dt.unixtime();
ThRec.lastRecTime = getUnixtimeFromSysClock();
//Serial << "Time: " << ThRec.lastRecTime <<endl;
}
uint32_t getUnixtimeFromSysClock() {
uint32_t t;
uint16_t days = date2days(sysClock[YEAR], sysClock[MON], sysClock[DATE]);
t = time2long(days, sysClock[HOUR], sysClock[MIN], sysClock[SEC]);
//t += 946684800; //SECONDS_FROM_1970_TO_2000
return t;
}
static const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
if (y >= 2000)
y -= 2000;
uint16_t days = d;
for (uint8_t i = 1; i < m; ++i)
days += pgm_read_byte(daysInMonth + i - 1);
if (m > 2 && y % 4 == 0)
++days;
return days + 365 * y + (y + 3) / 4 - 1;
}
static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
return ((days * 24L + h) * 60 + m) * 60 + s;
}
void syncRTC() {
DateTime dt = rtcLib.now();
sysClock[YEAR] = dt.year() - 2000;
sysClock[MON] = dt.month();
sysClock[DATE] = dt.day();
sysClock[HOUR] = dt.hour();
sysClock[MIN] = dt.minute();
sysClock[SEC] = dt.second();
//Serial << "RTC Unixtime: " << dt.unixtime() <<", sysClock Unixtime: " << getUnixtimeFromSysClock() << endl;
}
void checkBtCmd() {
if ( ! BTserial.available() || led.getState() == BUSY)return;
char c = BTserial.read();
uint32_t startTime=millis();
//Serial.print("Receive command from Bluetooth: ");
//Serial << c << "(0x" ;
//Serial.print(c , HEX);
//Serial.println(')');
if (c == 'S') {
bool battery = rtc.oscillatorCheck();
int temp = (int)(sht31.readTemperature() + 0.5);
int hmd = (int)(sht31.readHumidity() + 0.5);
//Serial << "Write uptimer: " << upTimer << endl;
BTserial.write(upTimer >> 24);
BTserial.write(upTimer >> 16);
BTserial.write(upTimer >> 8);
BTserial.write(upTimer);
BTserial.write(battery);
BTserial.write(recInterval);
BTserial.write(ThRec.lastRecAddr >> 8);
BTserial.write(ThRec.lastRecAddr);
BTserial.write(temp);
BTserial.write(hmd);
}
else if (c == 'D') {
// Data format: dataLen(2byte), recInterval(byte), isIncludeTemp(byte), lastRecAddr(2byte), lastRecTime(4byte), data ...
uint16_t dataLength = extRomSize + innerRomEndAddr - innerRomStartAddr + 1 + 8; // 8 is meta data length.
byte isIncludeTemp = ((isRecTemp) ? (byte)1 : (byte)0);
BTserial.write(dataLength >> 8);
BTserial.write(dataLength);
BTserial.write(recInterval);
BTserial.write(isIncludeTemp);
BTserial.write(ThRec.lastRecAddr >> 8);
BTserial.write(ThRec.lastRecAddr);
BTserial.write(ThRec.lastRecTime >> 24);
BTserial.write(ThRec.lastRecTime >> 16);
BTserial.write(ThRec.lastRecTime >> 8);
BTserial.write(ThRec.lastRecTime);
//Serial << "Record Interval: " << recInterval << endl;
//Serial << "Last Record Index: " << ThRec.lastRecAddr << endl;
//Serial << "Last record time: " << ThRec.lastRecTime << endl;
led.busy();
//Serial.println("Dump data from external ROM ...");
dumpExtRom();
dumpInnerRom();
//Serial.println("Dump Finish.");
led.unBusy();
}
else if (c == 'T') {
// If Command is set time, then set clock time immediately no matter how system busy.
int recv[6];
for (int i = 0; i < 6; i++) {
while(!BTserial.available()){
if(millis()-startTime>=4000)return; // If it no data come over 4 second, skip command.
}
recv[i] = BTserial.read();
}
setClock(recv[0], recv[1], recv[2], recv[3], recv[4], recv[5]);
}
else if ( c == 'I') {
// For setting humidity record interval.
while(!BTserial.available()){
if(millis()-startTime>=4000)return; // If it no data come over 4 second, skip command.
}
int interval = BTserial.read();
//Serial.print("Get set record interval command: "); Serial.println(interval);
recInterval = interval;
EEPROM.write(ADDR_REC_INTERVAL, interval);
ThRec.lastRecAddr = 0;
}
else if ( c == 'B') {
// For setting brightness of 4 digital time LED.
while(!BTserial.available()){
if(millis()-startTime>=4000)return; // If it no data come over 4 second, skip command.
}
int b = BTserial.read();
//Serial.print("Get brightness command: ");
//Serial.println(b, DEC);
if (b < 8) { // B指令後接收的資料必需<7,最後送出0xFF結束命令。
//Serial.println("Set value to Clock LED.");
clockLedBright = b;
tm1637.set(b);
}
else if (b == 127) { // 當送出0x127後將數值存起來
//Serial.println("Get FINAL brightness command, save value to ROM.");
int bright = EEPROM.read(ADDR_BRIGHTNESS);
if (bright == clockLedBright)Serial.println("The value same as value in ROM, skip save.");
if (bright != clockLedBright) {
//Serial.print("Write new bright value to ROM: ");
//Serial.println(clockLedBright);
EEPROM.write(ADDR_BRIGHTNESS, clockLedBright);
}
}
}
}
void showClockLed() {
if (!isBluetoothConnected && (wakeupPhase == CLOCK_WAKEUP) && (tm1637._PointFlag == POINT_OFF)
|| isBluetoothConnected && (millis() - clockSecMillis < 500) && (tm1637._PointFlag == POINT_OFF)
) {
//Serial.println("Set number of clock LED.");
//Serial.println("Colon ON");
tm1637.point(true);
tm1637.display(0, sysClock[HOUR] / 10); // hour
tm1637.display(1, sysClock[HOUR] % 10);
tm1637.display(2, sysClock[MIN] / 10); // minutes
tm1637.display(3, sysClock[MIN] % 10); //
}
if (!isBluetoothConnected && (wakeupPhase == WDT_WAKEUP) && (tm1637._PointFlag == POINT_ON)
|| isBluetoothConnected && (millis() - clockSecMillis > 500) && (tm1637._PointFlag == POINT_ON)
) {
//Serial.println("Turn off colon of clock LED.");
//Serial.println("Colon OFF");
tm1637.point(false);
tm1637.display(0, sysClock[HOUR] / 10); // hour
tm1637.display(1, sysClock[HOUR] % 10);
tm1637.display(2, sysClock[MIN] / 10); // minutes
tm1637.display(3, sysClock[MIN] % 10); //
}
}
// Set time to RTC DS3231
void setClock(byte year, byte mon, byte dayOfMonth, byte hour, byte min, byte sec) {
unsigned long startTime = millis();
uint16_t y = (uint16_t)year;
//Serial << "y = " << y << endl;
rtc.setYear(y); // use the time_t value to ensure correct weekday is set
rtc.setMonth(mon);
rtc.setDate(dayOfMonth);
rtc.setHour(hour);
rtc.setMinute(min);
rtc.setSecond(sec);
unsigned long spendTime = millis() - startTime;
//Serial.print("RTC set to: ");
//Serial << y <<":" <<mon <<":"<<dayOfMonth<<"_"<<hour<<":"<<min<<":"<<sec<<endl;
//Serial << " spend " << spendTime << "ms." << endl;
syncRTC();
//rtc.adjust(DateTime(year, mon, dayOfMonth, hour, min, sec));
}
bool isDevExist(int addr) {
Wire.beginTransmission(addr);
int result = Wire.endTransmission();
return (result == 0);
}
void accessEeprom() {
const byte address = 31;
//Serial.println("Read byte from EEPROM memory before write...");
byte data = extRom->readByte(address);
delay(10);
extRom->writeByte(address, ((data == 0xFF) ? 0xAA : 0xFF));
delay(10);
data = extRom->readByte(address);
}
void dumpExtRom() {
int c = 0;
for (unsigned int addr = 0; addr < extRomSize; addr += 32) {
//Serial.print("Read address ");
//Serial.println(addr);
//extRom->readBytes(addr, 32, data);
readRom(addr, data, 0, 32);
loop();
// Write to BTserial.
if (led.getState() == READY) return; // Abord command.
BTserial.write(data, 32);
loop();
//}
}
BTserial.flush();
}
void dumpInnerRom() {
int addr = 0;
for (int i = 0; i < 40; i++) {
for (int j = 0; j < 25; j++) {
data[j] = EEPROM.read(addr++);
}
loop();
BTserial.write(data, 25);
loop();
}
BTserial.flush();
}
void writeRom(unsigned int address, byte * data, int offset, int n) {
Wire.beginTransmission(EEPROM_ADDRESS);
//if (Wire.endTransmission()==0) {
Wire.beginTransmission(EEPROM_ADDRESS);
Wire.write(address >> 8);
Wire.write(address & 0xFF);
byte *adr = data + offset;
Wire.write(adr, n);
Wire.endTransmission();
delay(10);
}
// Read less then 32byte everytime.
void readRom(unsigned int address, byte * data, int offset, int n) {
//unsigned int dataAddr=address & (uint32_t)0x00000fff;
Wire.beginTransmission(EEPROM_ADDRESS);
if (Wire.endTransmission() == 0) {
Wire.beginTransmission(EEPROM_ADDRESS);
Wire.write(address >> 8);
Wire.write(address & 0xFF);
if (Wire.endTransmission() == 0) {
int r = 0;
Wire.requestFrom(EEPROM_ADDRESS, n);
while (Wire.available() > 0 && r < n) {
data[offset + r] = (byte)Wire.read();
r++;
}
}
}
else {
//Serial.print("I2C bus not ready while readRom()!!!");
led.loopErrSignal(NO_EEPROM);
}
}
Comments