Skip to content

Instantly share code, notes, and snippets.

@thedod
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thedod/d38456f66a7254422041 to your computer and use it in GitHub Desktop.
Save thedod/d38456f66a7254422041 to your computer and use it in GitHub Desktop.
16x2 LCD thermometer with S/M/H history graphs
/*
datalogger.cpp - yet another buffer object.
Created by thedod[.github.io].
Released into the public domain.
*/
#include "datalogger.h"
DataLogger::DataLogger(float *buff, unsigned int buffsize) {
_buffsize = buffsize;
_buff = buff;
nsamples = _next_push = 0;
}
void DataLogger::push(float value) {
_buff[_next_push] = value;
_next_push = (_next_push+1)%_buffsize;
if (nsamples < _buffsize) nsamples++;
}
void DataLogger::popReset(void) {
_pop_next = (_next_push+_buffsize-1)%_buffsize;
_pop_depth = nsamples;
}
float DataLogger::pop(void) {
if (!_pop_depth) return NOTHING;
float value = _buff[_pop_next];
_pop_depth--;
_pop_next = (_pop_next+_buffsize-1)%_buffsize;
return value;
}
void DataLogger::aggregate(unsigned int n) {
if (nsamples<n) n = nsamples;
if (n) {
agg_min = MUCH; agg_max = NOTHING; agg_avg = 0;
popReset();
for (int i = 0 ; i< n ; i++) {
float value = pop();
if (value<agg_min) agg_min = value;
if (value>agg_max) agg_max = value;
agg_avg += value/n;
}
} else {
agg_min = agg_max = agg_avg = NOTHING;
}
}
/*
datalogger.cpp - yet another buffer object.
Created by thedod[.github.io].
Released into the public domain.
*/
#ifndef Datalogger_h
#define Datalogger_h
#define NOTHING -3E38; // "almost -infinity" ;)
#define MUCH 3E38; // "almost infinity"
class DataLogger {
public:
DataLogger(float *buff, unsigned int buffsize);
int nsamples;
void push(float value);
void popReset(); // pop() would start popping from latest push.
float pop(); // May return NOTHING if buffer's finished.
void aggregate(unsigned int n); // compute agg_* (below) for latest [max] n
float agg_min, agg_max, agg_avg; // results of aggregate() undefined if not called
private:
int _buffsize, _next_push, _pop_next, _pop_depth;
float *_buff;
};
#endif
/*
rgb_lcd.cpp
2013 Copyright (c) Seeed Technology Inc. All right reserved.
Author:Loovee
2013-9-18
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <Wire.h>
#include "rgb_lcd.h"
void i2c_send_byte(unsigned char dta)
{
Wire.beginTransmission(LCD_ADDRESS); // transmit to device #4
Wire.write(dta); // sends five bytes
Wire.endTransmission(); // stop transmitting
}
void i2c_send_byteS(unsigned char *dta, unsigned char len)
{
Wire.beginTransmission(LCD_ADDRESS); // transmit to device #4
for(int i=0; i<len; i++)
{
Wire.write(dta[i]);
}
Wire.endTransmission(); // stop transmitting
}
rgb_lcd::rgb_lcd()
{
}
void rgb_lcd::begin(uint8_t cols, uint8_t lines, uint8_t dotsize)
{
Wire.begin();
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;
_currline = 0;
// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != 0) && (lines == 1)) {
_displayfunction |= LCD_5x10DOTS;
}
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
delayMicroseconds(50000);
// this is according to the hitachi HD44780 datasheet
// page 45 figure 23
// Send function set command sequence
command(LCD_FUNCTIONSET | _displayfunction);
delayMicroseconds(4500); // wait more than 4.1ms
// second try
command(LCD_FUNCTIONSET | _displayfunction);
delayMicroseconds(150);
// third go
command(LCD_FUNCTIONSET | _displayfunction);
// finally, set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction);
// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();
// clear it off
clear();
// Initialize to default text direction (for romance languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);
// backlight init
setReg(0, 0);
setReg(1, 0);
setReg(0x08, 0xAA); // all led control by pwm
setColorWhite();
}
/********** high level commands, for the user! */
void rgb_lcd::clear()
{
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}
void rgb_lcd::home()
{
command(LCD_RETURNHOME); // set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}
void rgb_lcd::setCursor(uint8_t col, uint8_t row)
{
col = (row == 0 ? col|0x80 : col|0xc0);
unsigned char dta[2] = {0x80, col};
i2c_send_byteS(dta, 2);
}
// Turn the display on/off (quickly)
void rgb_lcd::noDisplay()
{
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void rgb_lcd::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turns the underline cursor on/off
void rgb_lcd::noCursor()
{
_displaycontrol &= ~LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void rgb_lcd::cursor() {
_displaycontrol |= LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turn on and off the blinking cursor
void rgb_lcd::noBlink()
{
_displaycontrol &= ~LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void rgb_lcd::blink()
{
_displaycontrol |= LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// These commands scroll the display without changing the RAM
void rgb_lcd::scrollDisplayLeft(void)
{
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void rgb_lcd::scrollDisplayRight(void)
{
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}
// This is for text that flows Left to Right
void rgb_lcd::leftToRight(void)
{
_displaymode |= LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This is for text that flows Right to Left
void rgb_lcd::rightToLeft(void)
{
_displaymode &= ~LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This will 'right justify' text from the cursor
void rgb_lcd::autoscroll(void)
{
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This will 'left justify' text from the cursor
void rgb_lcd::noAutoscroll(void)
{
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void rgb_lcd::createChar(uint8_t location, uint8_t charmap[])
{
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
unsigned char dta[9];
dta[0] = 0x40;
for(int i=0; i<8; i++)
{
dta[i+1] = charmap[i];
}
i2c_send_byteS(dta, 9);
}
/*********** mid level commands, for sending data/cmds */
// send command
inline void rgb_lcd::command(uint8_t value)
{
unsigned char dta[2] = {0x80, value};
i2c_send_byteS(dta, 2);
}
// send data
inline size_t rgb_lcd::write(uint8_t value)
{
unsigned char dta[2] = {0x40, value};
i2c_send_byteS(dta, 2);
return 1; // assume sucess
}
void rgb_lcd::setReg(unsigned char addr, unsigned char dta)
{
Wire.beginTransmission(RGB_ADDRESS); // transmit to device #4
Wire.write(addr);
Wire.write(dta);
Wire.endTransmission(); // stop transmitting
}
void rgb_lcd::setRGB(unsigned char r, unsigned char g, unsigned char b)
{
setReg(REG_RED, r);
setReg(REG_GREEN, g);
setReg(REG_BLUE, b);
}
const unsigned char color_define[4][3] =
{
{255, 255, 255}, // white
{255, 0, 0}, // red
{0, 255, 0}, // green
{0, 0, 255}, // blue
};
void rgb_lcd::setColor(unsigned char color)
{
if(color > 3)return ;
setRGB(color_define[color][0], color_define[color][1], color_define[color][2]);
}
/*
rgb_lcd.h
2013 Copyright (c) Seeed Technology Inc. All right reserved.
Author:Loovee
2013-9-18
add rgb backlight fucnction @ 2013-10-15
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __RGB_LCD_H__
#define __RGB_LCD_H__
#include <inttypes.h>
#include "Print.h"
// Device I2C Arress
#define LCD_ADDRESS (0x7c>>1)
#define RGB_ADDRESS (0xc4>>1)
// color define
#define WHITE 0
#define RED 1
#define GREEN 2
#define BLUE 3
#define REG_RED 0x04 // pwm2
#define REG_GREEN 0x03 // pwm1
#define REG_BLUE 0x02 // pwm0
#define REG_MODE1 0x00
#define REG_MODE2 0x01
#define REG_OUTPUT 0x08
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
class rgb_lcd : public Print
{
public:
rgb_lcd();
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);
void clear();
void home();
void noDisplay();
void display();
void noBlink();
void blink();
void noCursor();
void cursor();
void scrollDisplayLeft();
void scrollDisplayRight();
void leftToRight();
void rightToLeft();
void autoscroll();
void noAutoscroll();
void createChar(uint8_t, uint8_t[]);
void setCursor(uint8_t, uint8_t);
virtual size_t write(uint8_t);
void command(uint8_t);
// color control
void setRGB(unsigned char r, unsigned char g, unsigned char b); // set rgb
void setPWM(unsigned char color, unsigned char pwm){setReg(color, pwm);} // set pwm
void setColor(unsigned char color);
void setColorAll(){setRGB(0, 0, 0);}
void setColorWhite(){setRGB(255, 255, 255);}
using Print::write;
private:
void send(uint8_t, uint8_t);
void setReg(unsigned char addr, unsigned char dta);
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _initialized;
uint8_t _numlines,_currline;
};
#endif
/* Thermoscope - a 16x2 LCD thermometer with S/M/H history graphs. See
http://www.instructables.com/id/Thermoscope-16x2-LCD-thermometer-with-SMH-graphs
Done using[1]:
* arduino[2]
* grove shield
* Grove RGB LCD (goes to I2C)
* Grove Temperature sensor (goes to A0)
* Grove button (goes to D4)
Button toggles between Second/Minute/Hour modes.
Current temperature (top left) is updated every second (regardless of mode).
Graph shows normalized average temperatures for last [up to] 8 H/M/S.
Second line shows minimum-maximum values for the normalized graph to give
a sense of scale (e.g. if they're 0.1 degrees apart, what you see is noise).
This code (and the datalogger library) can also be useful as a
"poor people's scope" for other kinds of sensors.
----
[1] All the components come from a seestudio starter pack. Tips for alternatives:
* LCD: code should work with any i2c or parallel 16x2 lcd display out there.
Once you find the right class, they have very similar methods.
* Temperature sensor: I'm sure there's something similar out there,
but analog2celsius might be different depending on the thermistor.
* Button: if you're using a "raw" momentary switch, also connect its output
to gnd via a 10K-ohm resistor (pull-down) to make sure we get a LOW
[and not noise] when the button isn't pressed.
There are also buttons with a built-in resistor (they have 3 pins:
gnd, vcc, and signal).
[2] Note that if you have an old arduino without the sda and scl pins
you'll need patch them to a4 and a5 on the grove shield's headers. See photo at
http://www.instructables.com/id/Thermoscope-16x2-LCD-thermometer-with-SMH-graphs
*/
#include <Wire.h>
#include "rgb_lcd.h"
#include "datalogger.h"
rgb_lcd lcd;
const unsigned char DEGSYMBOL = 7;
byte degSymbol[8] = {
0b00000,
0b01100,
0b10010,
0b10010,
0b01100,
0b00000,
0b00000,
0b00000
};
byte barSymbol[8]; // temporary for generating bar chars
const int SECONDS = 0;
const int MINUTES = 1;
const int HOURS = 2;
float seconds[60];
float minutes[60];
float hours[24]; // Actually, we could do with 8, but 24 is a sensible number ;)
DataLogger loggers[] = { DataLogger(seconds,60), DataLogger(minutes,60), DataLogger(hours,24) };
char *logger_names[] = { "S","M","H" };
int current_logger;
unsigned long sample;
const int BUTTON_PIN = 4;
bool button_was_on;
void setup()
{
initLcd(lcd);
lcd.clear();
sample = 0;
current_logger = SECONDS;
button_was_on = false;
}
void loop() {
int a = analogRead(0);
float t = analog2Celsius(a);
sample++;
lcd.setCursor(0, 0);
lcd.print(t);
lcd.write(DEGSYMBOL);
lcd.print(" "); // Voodoo against 10->9 ;)
loggers[SECONDS].push(t);
if (!(sample%60)) {
loggers[SECONDS].aggregate(60);
loggers[MINUTES].push(loggers[SECONDS].agg_avg);
if (!(sample%3600)) {
loggers[MINUTES].aggregate(60);
loggers[HOURS].push(loggers[MINUTES].agg_avg);
}
}
lcd.setCursor(7,0);
lcd.print(logger_names[current_logger]);
lcd.setCursor(8,0);
if (loggers[current_logger].nsamples) {
// Find min/max for graph normalization
loggers[current_logger].aggregate(8);
long min1k = 1000*loggers[current_logger].agg_min;
long max1k = 1000*loggers[current_logger].agg_max;
// Draw graph
loggers[current_logger].popReset();
for (int i = 0 ; i<8 ; i++) {
float value = loggers[current_logger].pop();
lcd.setCursor(15-i, 0);
if (value>0) {
lcd.write((unsigned char)map((long)(1000*value),min1k,max1k,0,6));
} else { // We've started less than 8 S/M/H ago. This one's empty.
lcd.print('-');
}
}
lcd.setCursor(0, 1);
lcd.print(loggers[current_logger].agg_min);
lcd.print("-");
lcd.print(loggers[current_logger].agg_max);
lcd.write(DEGSYMBOL);
lcd.print(" "); // Voodoo against 10->9 ;)
} else {
lcd.setCursor(8,0);
lcd.print("--------");
lcd.setCursor(0,1);
lcd.print("No data yet...");
}
// Countdown for M/H modes
lcd.setCursor(14, 1);
if (current_logger) {
int unit = pow(60,current_logger-1);
int countdown = 60-(sample/unit)%60;
if (countdown<10) lcd.print(" ");
lcd.print(countdown);
} else {
lcd.print(" ");
}
for (int i=0; i<10; i++) {
checkButton();
delay(100);
}
}
float analog2Celsius(int a) {
// Based on http://www.instructables.com/id/grove-minimal-thermometer/
const int B=3975; // Value of the thermistor
float resistance=(float)(1023-a)*10000/a;
return 1/(log(resistance/10000)/B+1/298.15)-273.15;
}
void initLcd(rgb_lcd lcd) {
lcd.begin(16, 2);
//lcd.setRGB(63, 63, 63); // Don't burn photos ;)
lcd.createChar(DEGSYMBOL, degSymbol);
for (int i = 0 ; i<8 ; i++) barSymbol[i] = 0b00000;
lcd.createChar(0,barSymbol);
for (int level = 0 ; level<7 ; level++) {
barSymbol[7-level] = 0b01110;
lcd.createChar(level,barSymbol);
}
}
void checkButton(void) {
int b = digitalRead(BUTTON_PIN);
if (b) {
if (!button_was_on) current_logger = (current_logger+1)%3;
button_was_on = true;
} else {
button_was_on = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment