Skip to content

Instantly share code, notes, and snippets.

@adamzaninovich
Created September 25, 2012 18:33
  • Star 15 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
Star You must be signed in to star a gist
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.
/*
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);
}
/**
* QuadEncoder.cpp - Library for reading moves from a quadrature rotary encoder
* Created by Pedro Rodrigues (medecau@gmail.com) 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 '-';
}
}
}
}
/**
* QuadEncoder.h - Library for reading moves from a quadrature rotary encoder
* Created by Pedro Rodrigues (medecau@gmail.com) 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
@CoffeeTronics
Copy link

I would really like to use your rotary encoder menu system for a project I am working on. Could you pls post the "button" library?

@adamzaninovich
Copy link
Author

This Tap library is better than my Button one, I use this now: https://github.com/d2kagw/arduino-tap-library

@aleza66
Copy link

aleza66 commented Oct 28, 2014

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?

@phsheth
Copy link

phsheth commented Jun 6, 2017

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