Created
September 25, 2012 18:33
-
-
Save adamzaninovich/3783602 to your computer and use it in GitHub Desktop.
Code used on demo project shown at (http://www.youtube.com/watch?v=a1M5kirA2_8) and (http://www.youtube.com/watch?v=epJ3aSzrsXQ) Includes climate sensing, logging to SD card, HTTP server with JSON output, LCD display with settings menus.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Displays the current temperature and relative | |
humidity on an LCD with an common cathode RGB | |
backlight. | |
*/ | |
// Include Standard Library | |
#include <stdlib.h> | |
// Include LCD Library Code | |
#include <LiquidCrystal.h> | |
// Include RHT Library Code | |
#include <DHT22.h> | |
// Include SPI and Ethernet Libraries | |
#include <SPI.h> | |
#include <Ethernet.h> | |
// Include SD for accessing the sd card | |
#include <SD.h> | |
// Include OneWire for reading the ds temperature sensor | |
#include <OneWire.h> | |
// Include Rotary Encoder Library | |
#include <QuadEncoder.h> | |
#include <Button.h> | |
// input | |
#define TEMP 27 | |
#define SND1 28 | |
#define PIR1 29 | |
#define RHT 30 | |
#define PIR2 31 | |
#define LIGHT 15 // A15 | |
// control | |
#define ROTENCA 43 | |
#define ROTENCB 45 | |
#define BUTTONR 2 // Intr 0 = Pin 2 | |
#define BUTTON 1 // Intr 1 = Pin 3 | |
// LCD | |
#define LCD_RS 42 | |
#define LCD_EN 44 | |
#define LCD_D1 46 | |
#define LCD_D2 48 | |
#define LCD_D3 47 | |
#define LCD_D4 49 | |
#define LCD_BLR 5 | |
#define LCD_BLG 6 | |
#define LCD_BLB 7 | |
// OUTPUT | |
#define LED1 41 | |
#define LED2 40 | |
#define LED3 39 | |
#define LED4 38 | |
#define LED5 37 | |
#define LED6 36 | |
#define LED7 35 | |
#define LED8 34 | |
#define LED9 33 | |
#define LED10 32 | |
int leds[] = { LED1, LED2, LED3, LED4, LED5, | |
LED6, LED7, LED8, LED9, LED10 }; | |
// reserved | |
#define SD_SS 4 | |
#define ETH_SS 10 | |
#define SPI_MOSI 50 | |
#define SPI_MISO 51 | |
#define SPI_CLK 52 | |
#define HW_SS 53 | |
// modes | |
#define MODE_DISPLAY 0 | |
#define MODE_COUNT 1 | |
#define LAST_MODE 1 | |
// Setup OneWire Temperature Sensor | |
byte tempAddr[] = { 0x28, 0x6C, 0xAC, 0x49, 0x03, 0x00, 0x00, 0x25 }; | |
OneWire ds(TEMP); | |
// buffer for float to string conversion | |
char buffer[25]; | |
// Setup MAC address and IP address for the Ethernet card | |
byte mac[] = { 0x90, 0xA2, 0xDA, 0x04, 0x00, 0xF1 }; | |
IPAddress ip(192,168,1,5); | |
// Initialize the Ethernet server library | |
// with the IP address and port you want to use | |
// (port 80 is default for HTTP): | |
EthernetServer server(80); | |
// initialize the LCD with the numbers of the interface pins | |
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D1, LCD_D2, LCD_D3, LCD_D4); | |
// initialize RHT with data pin | |
DHT22 rht(RHT); | |
// initialize the encoder | |
QuadEncoder encoder(ROTENCA,ROTENCB); // initialize the encoder | |
// has the encoder moved on this loop? | |
boolean moved = false; | |
Button buttonR(BUTTONR); | |
// color levels for the backlight | |
int r_level = 255; | |
int g_level = 255; | |
int b_level = 255; | |
// data vars | |
int motionUp = LOW; | |
int motionDn = LOW; | |
int sound = LOW; | |
double temperature; | |
double humidity; | |
double light; | |
unsigned long lastReading = 0; | |
int temperatureUnit = 'F'; | |
boolean sdcard = false; | |
// last data vars | |
int lastMotionUp = motionUp; | |
int lastMotionDn = motionDn; | |
int lastsound = sound; | |
double lastTemperature = temperature; | |
double lastLight = light; | |
// other vars | |
int mode = MODE_DISPLAY; | |
int count = 0; | |
unsigned long timeSinceLastDisplay = 0; | |
boolean ledIsOn = false; | |
// Custom character for a degree sign | |
byte degree[8] = { | |
B01100, | |
B10010, | |
B10010, | |
B01100, | |
B00000, | |
B00000, | |
B00000 | |
}; | |
void setup() { | |
// setup interrupts | |
attachInterrupt(BUTTON, displayMenu, RISING); | |
// set the LCD's number of cols and lines | |
lcd.begin(16, 2); | |
lcd.createChar(0, degree); | |
// turn on backlight | |
ledOn(); | |
// Start Ethernet connection | |
Ethernet.begin(mac, ip); | |
// Start listening | |
server.begin(); | |
// initialize pins for RGB backlight | |
pinMode(LCD_BLR, OUTPUT); | |
pinMode(LCD_BLG, OUTPUT); | |
pinMode(LCD_BLB, OUTPUT); | |
// initialize inputs | |
pinMode(PIR1, INPUT); | |
pinMode(PIR2, INPUT); | |
pinMode(SND1, INPUT); | |
// print a welcome message | |
lcd.clear(); | |
lcd.print("*** WELCOME! ***"); | |
lcd.setCursor(0,1); | |
lcd.print("Initializing"); | |
// initialize led bar while delaying 2000ms giving the rht time to warm up | |
for (int i=0; i<10; ++i) { | |
if (i%3 == 0) lcd.print("."); | |
pinMode(leds[i], OUTPUT); | |
digitalWrite(leds[i], HIGH); | |
delay(100); | |
} | |
for (int i=0; i<10; ++i) { | |
digitalWrite(leds[i], LOW); | |
delay(100); | |
} | |
// Initialize sd card | |
if (!SD.begin(SD_SS)) { | |
Serial.println("Error initializing card"); | |
sdcard = false; | |
} else { | |
writeLog(",,,,,,"); | |
} | |
} | |
void loop() { | |
ledStatus(); | |
populateData(); | |
displayData(); | |
runServer(); | |
if (!sound && !motionUp && !motionDn) autoBrightness(); | |
if (sound) ledColor(0,0,b_level); | |
log(); | |
if (motionUp || motionDn) ledColor(r_level,0,0); | |
} | |
void ledStatus() { | |
if (sdcard) { | |
digitalWrite(leds[0], HIGH); | |
digitalWrite(leds[9], HIGH); | |
} else { | |
digitalWrite(leds[0], LOW); | |
digitalWrite(leds[9], LOW); | |
} | |
} | |
void log() { | |
if (sdcard) { | |
boolean logTemperature = (changed(lastTemperature,temperature)); | |
boolean logMotionUp = (changed(lastMotionUp, motionUp) && motionUp == HIGH); | |
boolean logMotionDn = (changed(lastMotionDn, motionDn) && motionDn == HIGH); | |
if (sound || logTemperature || logMotionUp || logMotionDn) { | |
String data = String(millis(), DEC) + ", "; | |
data += sound ? "1" : "0"; | |
data += ", "; | |
data += motionUp ? "1" : "0"; | |
data += ", "; | |
data += motionDn ? "1" : "0"; | |
data += ","; | |
data += dtostrf(temperature, 6, 2, buffer); | |
data += ","; | |
data += dtostrf(humidity, 6, 2, buffer); | |
data += ","; | |
data += dtostrf(light, 6, 2, buffer); | |
writeLog(data); | |
for (int j=1; j<9; ++j) { | |
digitalWrite(leds[j], HIGH); | |
delay(10); | |
for (int i=1; i<9; ++i) digitalWrite(leds[i], LOW); | |
} | |
} | |
} else delay(30); | |
} | |
void writeLog(String data) { | |
if (sdcard) { | |
File dataFile = SD.open("datalog.txt", FILE_WRITE); | |
if (dataFile) { | |
dataFile.println(data); | |
dataFile.close(); | |
} | |
} | |
} | |
boolean changed(int last, int current) { | |
return (last != current); | |
} | |
boolean changed(double last, double current) { | |
return (last != current); | |
} | |
// Displays the current values on the LCD | |
void displayData() { | |
if (displayOk()) { | |
lcd.clear(); | |
lcd.print("CLIMATE L"); | |
lcd.print(light); | |
lcd.print("%"); | |
lcd.setCursor(0,1); | |
lcd.print("T"); | |
if (temperatureUnit == 'C') { | |
lcd.print(temperature); | |
lcd.write((uint8_t)0); | |
lcd.print("C H"); | |
} else { | |
lcd.print(c2f(temperature)); | |
lcd.write((uint8_t)0); | |
lcd.print("F H"); | |
} | |
lcd.print(humidity); | |
lcd.print("%"); | |
} | |
} | |
// Check for requests | |
void runServer() { | |
EthernetClient client = server.available(); | |
if (client) { | |
// display something | |
lcd.setCursor(0,0); | |
lcd.print("CLIENT CONNECTED"); | |
ledColor(0,g_level,0); | |
// an http request ends with a blank line | |
boolean currentLineIsBlank = true; | |
while (client.connected()) { | |
if (client.available()) { | |
char c = client.read(); | |
// if you've gotten to the end of the line (received a newline | |
// character) and the line is blank, the http request has ended, | |
// so you can send a reply | |
if (c == '\n' && currentLineIsBlank) { | |
// send a standard http response header | |
client.println("HTTP/1.1 200 OK"); | |
client.println("Content-Type: application/json"); | |
client.println("Access-Control-Allow-Origin: *"); | |
client.println(); | |
client.print("{\"temperature\":"); | |
client.print(temperature); | |
client.print(",\"humidity\":"); | |
client.print(humidity); | |
client.print(",\"light\":"); | |
client.print(light); | |
// fix these to return true if they have been activated in the last x time | |
client.print(",\"motion_up\":"); | |
if (motionUp) client.print("true"); | |
else client.print("false"); | |
client.print(",\"motion_down\":"); | |
if (motionDn) client.print("true"); | |
else client.print("false"); | |
client.print(",\"sound\":"); | |
if (sound) client.print("true"); | |
else client.print("false"); | |
client.println("}"); | |
break; | |
} | |
if (c == '\n') { | |
// you're starting a new line | |
currentLineIsBlank = true; | |
} else if (c != '\r') { | |
// you've gotten a character on the current line | |
currentLineIsBlank = false; | |
} | |
} | |
} | |
// give the web browser time to receive the data | |
delay(1); | |
client.stop(); | |
// display something | |
lcd.setCursor(0,0); | |
lcd.print("CLIENT FINISHED "); | |
if (ledIsOn) { | |
ledOn(); | |
} else { | |
ledOff(); | |
} | |
} | |
} | |
void displayMenu() { | |
int selected = 3; | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
lcd.print("Turn the knob to"); | |
lcd.setCursor(0,1); | |
lcd.print("select an option"); | |
// display options | |
while (!buttonR.pressed()) { | |
selected += readEncoder(); | |
if (selected<0) selected=3; | |
if (selected>3) selected=0; | |
if (moved) { | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
switch(selected) { | |
case 0: | |
lcd.print("Change Units "); | |
break; | |
case 1: | |
lcd.print("Backlight On/Off"); | |
break; | |
case 2: | |
lcd.print("Logging On/Off "); | |
break; | |
case 3: | |
lcd.print("Exit Settings "); | |
break; | |
} | |
} | |
} | |
// when button is pressed... | |
lcd.clear(); | |
switch(selected) { | |
case 0: | |
settingsChangeUnits(); | |
break; | |
case 1: | |
settingsChangeBacklight(); | |
break; | |
case 2: | |
settingsChangeLogging(); | |
} | |
} | |
void settingsChangeUnits() { | |
int selected = 2; | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
lcd.print("Select Unit "); | |
lcd.setCursor(0,1); | |
lcd.print("Currently it's "); | |
lcd.write(temperatureUnit); | |
while (!buttonR.pressed()) { | |
selected += readEncoder(); | |
if (selected<0) selected=2; | |
if (selected>2) selected=0; | |
if (moved) { | |
lcd.setCursor(0,1); | |
switch(selected) { | |
case 0: | |
lcd.print("Celcius "); | |
break; | |
case 1: | |
lcd.print("Fahrenheit "); | |
break; | |
case 2: | |
lcd.print("Cancel "); | |
break; | |
} | |
} | |
} | |
// when button is pressed... | |
switch(selected) { | |
case 0: | |
temperatureUnit = 'C'; | |
break; | |
case 1: | |
temperatureUnit = 'F'; | |
break; | |
} | |
} | |
void settingsChangeBacklight() { | |
boolean previousSetting = ledIsOn; | |
int selected = 2; | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
lcd.print("Backlight On/Off"); | |
while (!buttonR.pressed()) { | |
selected += readEncoder(); | |
if (selected<0) selected=2; | |
if (selected>2) selected=0; | |
if (moved) { | |
lcd.setCursor(0,1); | |
lcd.print(" On Off Cancel"); | |
switch(selected) { | |
case 0: | |
lcd.setCursor(0,1); | |
ledOn(); | |
break; | |
case 1: | |
lcd.setCursor(4,1); | |
ledOff(); | |
break; | |
case 2: | |
lcd.setCursor(9,1); | |
previousSetting ? ledOn() : ledOff(); | |
break; | |
} | |
lcd.write((uint8_t)126); // right arrow | |
} | |
} | |
} | |
void settingsChangeLogging() { | |
int selected = 2; | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
lcd.print("Logging to SD "); | |
lcd.setCursor(0,1); | |
if (sdcard) lcd.print("card is: On "); | |
else lcd.print("card is: Off "); | |
while (!buttonR.pressed()) { | |
selected += readEncoder(); | |
if (selected<0) selected=2; | |
if (selected>2) selected=0; | |
if (moved) { | |
lcd.setCursor(0,1); | |
switch(selected) { | |
case 0: | |
if (sdcard) lcd.print("Keep Logging On "); | |
else lcd.print("Turn Logging On "); | |
break; | |
case 1: | |
if (sdcard) lcd.print("Turn Logging Off"); | |
else lcd.print("Keep Logging Off"); | |
break; | |
case 2: | |
lcd.print("Cancel "); | |
break; | |
} | |
} | |
} | |
// when button is pressed... | |
switch(selected) { | |
case 0: | |
sdcard = true; | |
break; | |
case 1: | |
sdcard = false; | |
break; | |
} | |
} | |
// this function populates the global data variables temperature | |
// and humidity. it is safe to run as often as you'd like, it will | |
// only read the sensor every 2000ms. | |
void populateData() { | |
// record last readings | |
lastLight = light; | |
lastTemperature = temperature; | |
lastMotionUp = motionUp; | |
lastMotionDn = motionDn; | |
lastsound = sound; | |
light = mapLight(analogRead(LIGHT)); | |
// temperature = readTemperature(); | |
motionDn = digitalRead(PIR1); | |
motionUp = digitalRead(PIR2); | |
sound = digitalRead(SND1); | |
// is this useful in other places? | |
unsigned long timeNow = millis(); | |
if (timeNow < lastReading) { | |
// millis() has overflowed, reset lastReading time | |
lastReading = timeNow; | |
} | |
if ((timeNow - lastReading) >= 2000) { | |
DHT22_ERROR_t errorCode = rht.readData(); | |
if (errorCode == DHT_ERROR_NONE) { | |
temperature = rht.getTemperatureC(); | |
humidity = rht.getHumidity(); | |
} | |
lastReading = timeNow; | |
} | |
} | |
// reads onewire ds temp sensor | |
double readTemperature() { | |
byte data[12]; | |
ds.reset(); | |
ds.select(tempAddr); | |
ds.write(0x44, 1); // start conversion, with parasite power on at the end | |
byte present = ds.reset(); | |
ds.select(tempAddr); | |
ds.write(0xBE); // Read Scratchpad | |
for (int i=0; i<9; ++i) data[i] = ds.read(); | |
ds.reset_search(); | |
byte MSB = data[1]; | |
byte LSB = data[0]; | |
double tempRead = ((MSB << 8) | LSB); // using 2's compliment | |
double tempSum = tempRead / 16; | |
return tempSum; | |
} | |
// same as map(light, 0, 990, 0, 100), but with decimals | |
double mapLight(int lightIn) { | |
return (double)lightIn * 100.0 / 990.0; | |
} | |
boolean displayOk() { | |
unsigned long now = millis(); | |
if (now - timeSinceLastDisplay >= 500) { | |
timeSinceLastDisplay = now; | |
return true; | |
} else return false; | |
} | |
// automatically adjusts backlight brightness depending on light sensor reading | |
void autoBrightness() { | |
if (ledIsOn) { | |
// adjust backlight based on ambient light | |
double lightPercentage = (double)light/100.0; | |
r_level = 255 - (255 * lightPercentage); | |
g_level = 255 - (255 * lightPercentage); | |
b_level = 255 - (255 * lightPercentage); | |
ledOn(); | |
} else ledOff(); | |
} | |
// returns 1 for right, -1 for left, or 0 for no movement | |
int readEncoder() { | |
moved = true; | |
switch(encoder.hb()) { | |
case '>': return 1; | |
case '<': return -1; | |
} | |
moved = false; | |
return 0; | |
} | |
// converts celsius to fahrenheit | |
double c2f(double celsius) { | |
return 1.8 * celsius + 32; | |
} | |
// toggles the backlight | |
void toggleBacklight() { | |
ledIsOn ? ledOff() : ledOn(); | |
} | |
// turns on the backlight | |
void ledOn() { | |
ledIsOn = true; | |
ledColor(r_level,g_level,b_level); | |
} | |
// turns off the backlight | |
void ledOff() { | |
ledIsOn = false; | |
ledColor(0,0,0); | |
} | |
// sets the color of the backlight | |
void ledColor(int r, int g, int b) { | |
if (0 <= r && r <= 255) analogWrite(LCD_BLR, r); | |
if (0 <= g && g <= 255) analogWrite(LCD_BLG, g); | |
if (0 <= b && b <= 255) analogWrite(LCD_BLB, b); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* QuadEncoder.cpp - Library for reading moves from a quadrature rotary encoder | |
* Created by Pedro Rodrigues ([email protected]) 9, January of 2010 | |
* Released into the public domain. | |
*/ | |
#include "Arduino.h" | |
#include "QuadEncoder.h" | |
QuadEncoder::QuadEncoder(int pin1, int pin2) | |
{ | |
pinMode(pin1, INPUT); | |
pinMode(pin2, INPUT); | |
digitalWrite(pin1, HIGH); | |
digitalWrite(pin2, HIGH); | |
_inputPin1=pin1; | |
_inputPin2=pin2; | |
_val1=1; | |
_val2=1; | |
_oldVal1=1; | |
_oldVal2=1; | |
_pos=0; | |
_oldPos=0; | |
_turn=0; | |
_turnCount=0; | |
} | |
char QuadEncoder::hb() | |
{ | |
_val1 = digitalRead(_inputPin1); | |
_val2 = digitalRead(_inputPin2); | |
// Detect changes | |
if ( _val1 != _oldVal1 || _val2 != _oldVal2) { | |
_oldVal1=_val1; | |
_oldVal2=_val2; | |
//for each pair there's a position out of four | |
if ( _val1 == 1 ) {// stationary position | |
if (_val2 == 1) | |
_pos = 0; | |
else if (_val2 == 0) | |
_pos = 3; | |
} else if (_val1 == 0){ | |
if (_val2 == 1) | |
_pos = 1; | |
else if (_val2 == 0) | |
_pos = 2; | |
} | |
_turn = _pos-_oldPos; | |
_oldPos = _pos; | |
if (abs(_turn) != 2) { | |
if (_turn == 1 || _turn == -3) | |
_turnCount++; | |
else if (_turn == -1 || _turn == 3) | |
_turnCount--; | |
} | |
if (_pos==0){ | |
if (_turnCount>0){ | |
_turnCount=0; | |
return '>'; | |
} else if (_turnCount<0){ | |
_turnCount=0; | |
return '<'; | |
} else { | |
_turnCount=0; | |
return '-'; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* QuadEncoder.h - Library for reading moves from a quadrature rotary encoder | |
* Created by Pedro Rodrigues ([email protected]) 9, January of 2010 | |
* Released into the public domain. | |
*/ | |
#ifndef QuadEncoder_h | |
#define QuadEncoder_h | |
#include "Arduino.h" | |
class QuadEncoder | |
{ | |
public: | |
QuadEncoder(int pin1, int pin2); | |
char hb(); | |
private: | |
int _inputPin1; | |
int _inputPin2; | |
int _val1; | |
int _val2; | |
int _oldVal1; | |
int _oldVal2; | |
int _pos; | |
int _oldPos; | |
int _turn; | |
int _turnCount; | |
}; | |
#endif |
Hey I am also interested in the menu system, how does the tap library fits into this?
I just can to reuse the menu+rotary encoder?
while (!buttonR.pressed()) even when used with Tap library while (!buttonR.isHit()) is not very efficient - lot of misfiring. looking for ways to efficiently capture the rotary encoder click - do u have any improved library?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This Tap library is better than my Button one, I use this now: https://github.com/d2kagw/arduino-tap-library