Monthly Archives: January 2015

Level Shifting: Arduino & ESP8266

Using the ESP8266 WiFi Transceiver module with a 5v Arduino requires the use of level shifting circuitry. A resistor divider (1k and 2k typically) works perfectly well for signals from the 5v Arduino to the ESP8266 3.3v. However, while many people connect 3.3v derived signals directly to a 5v powered input, to maintain good noise margins requires an active interface. While there are several bus drivers available that will perform level translation and buffering (such as the 74LVC245A), they are overfill when just a couple of signals are involved.

In the design of the ESP8266 WiFi Module: Data Logger there are just two signals (TX and RX) that require level shifting and this circuit, using a couple of 2N7000 N-Channel Enhancement Mode Field Effect Transistors is small and works very well.

Level Shifting Interface

Level Shifting Interface

Now, I recognize that some of you may not be convinced that this actually works. Unfortunately this may be because I failed to draw in the drain-substrate diode of the MOS-FET (see state 3 below).

Operation of the level shifter:

The following three states should be considered during the operation of the level shifter:

  1. No device is pulling down the bus line. The bus line of the ‘lower-voltage’ section is pulled up by its pull-up resistors to 3.3V. The gate and the source of the MOS-FET are both at 3.3V, so its VGS is below the threshold voltage and the MOS-FET is not conducting. This allows the bus line at the ‘higher-voltage’ section to be pulled up by its pull-up resistor to 5V. So the bus lines of both sections are HIGH, but at a different voltage level.
  2. A 3.3V device pulls down the bus line to a LOW level. The source of the MOS-FET also becomes LOW, while the gate stays at 3.3V. VGS rises above the threshold and the MOS-FET starts to conduct. The bus line of the ‘higher-voltage’ section is then also pulled down to a LOW level by the 3.3V device via the conducting MOS-FET. So the bus lines of both sections go LOW to the same voltage level.
  3. A 5V device pulls down the bus line to a LOW level. The drain-substrate diode of the MOS-FET means that the ‘lower-voltage’ section is pulled down until VGS passes the threshold and the MOS-FET starts to conduct. The bus line of the ‘lower-voltage’ section is then further pulled down to a LOW level by the 5V device via the conducting MOS-FET. So the bus lines of both sections go LOW to the same voltage level.

OK?

To illustrate, I used the 32kHz square-wave signal from an RTC module. In the first instance, the RTC module was powered by 5v and in the second by 3.3v. The following screen capture from my oscilloscope shows the RTC 32KHz 5V signal (green) shifted to 3.3V (yellow). The on-screen measurements show the shift from 3.9V peak-to-peak (0.6 – 4.5V) to 2.9V peak-to-peak (0.4 – 3.3V).  Just what we wanted!

Level shifting from 5v to 3.3v

Level shifting from 5v to 3.3v. Notice the peak-to-peak measurements for each channel (green – input, yellow – output)

Now, when the RTC is powered from 3.3V the square wave has 2.9V peak-to-peak (0.6 – 3.3V). The trace below shows the shift to 3.3V peak-to-peak (0.5 – 3.8V).  For a typical CMOS gate operating at 5V, the acceptable input signal voltages range from 0 – 1.5V for a logic 0 state, and 3.5 – 5V for a logic 1 state. While several designs suggest a direct connection between a 3.3V output and 5V input is okay, if it works at all there is absolutely no noise margin as a 3.3V output is lower than the nominal minimal input voltage level for a logic 1 state.

However, level shifting from 3.3V outputs to 5V inputs using this design provides at least 0.3V of noise margin.

Level shifting from 3.3v to 5v

Level shifting from 3.3v to 5v.

Hope that this helps…

Just saying!

Large numbers on small displays…

Widely available and cheap, small 2 and 4-line LCD panels are convenient displays to add capability to  Arduino projects that require the display of information or data. With the “LiquidCrystal.h” library built into the Arduino IDE, the availability of both parallel and I2C interfaces, and a wide variety of bright and subtle backlight colours, they are simple to add to your Arduino projects and look great.

4 x 20 Character Liquid Crystal Display Module

4×20 Character Liquid Crystal Display Module

However, the popular 2 (line) x16 (column) and 4×20 LCDs appear to use the same HD44780 or equivalent LCD controller that has an inbuilt character set based on the 5×8 pixel ‘cell’ to display all alphanumeric text. This means that there is just one single-size font to produce characters of up to 4.5mm high. While this suits many applications, it is a limitation for projects such as clocks designed to be viewed from a distance.

So, for my latest “gpSKY CLOCK“, a GPS-based clock equipped with a 4×20 display, I have created a simple way to display numbers at 3x scale…  Yep, whopping great big 13mm high numbers!

gpSkyClock in clock mode

gpSkyClock in large time display mode

Continue reading

IR Remote Special Functions

After building the RGB LED Strip Alarm Clock, the software development continued with the addition of IR Remote functionality with operation of the preset colours and intensity controls. The four special functions – FLASH, STROBE, FADE and SMOOTH – have now been added to the software load. These four functions provide interesting dynamic light effects by directly controlling the LED strip. In a similar manner to other IR Remote functions, an alarm event overrides this control of the LED strip to execute the alarm light effect.

The new four IR Remote effects are shown here…

  1. The SMOOTH effect very slowly moves around the HSV colour wheel, starting from the hue and saturation of a selected colour preset.
  1. The FLASH effect quickly raises and lowers the intensity of the light at a hue selected from any of the preset colours.
  1. The FADE effect raises and lowers the intensity of the light while also rotating the hue – starting from a selected preset colour – around the colour wheel.

4. The STROBE effect pulses high intensity white light.

The Arduino code to perform these additional functions can be seen here…

[codesyntax lang=”php” title=”RGB Strip Alarm Clock – IR Remote Special Functions” blockstate=”collapsed”]

//****************************************************************************************
//                             LIGHT STRIP WAKE UP ALARM CLOCK 
//                      Samuel Rochefort and Adrian Jones, Janueary 2015
//****************************************************************************************
 
// Build 1
//   r1 141218 - initial build
//   r2 141219 - addition of date and alarm time and its setting
//   r3 141226 - triangle pulsing
//   r4 141227 - switches for mode control, timeout.
//   r6 141230 - addition of date setting, hue & saturation setting and immediate start of alarm effect
//   r7 141231 - addition of effects time variables to EEPROM and modes
//   r8 150103 - addition of IR receiver and control of strip colour (based on  Ken Shirriff's work http://arcfn.com )
//   r9 150109 - addition of IR functions FLASH, STROBE, FADE and SMOOTH
//               FLASH:  fast fade of preset colour up and down
//               STROBE: quickly flashes high intensity white
//               FADE:   slowly fades colour up and down and cycles through hue
//               SMOOTH: very slowly fades preset colour up and down
//*****************************************************************************************
#define build 1
#define revision 9
//*****************************************************************************************

// ARDUINO PIN USAGE
// LED Strip: D9 - red, D10 - green, D6 - blue
// LCD: D3-en,D4-rs,D5-d4,D12-d5,D7-d6,D8-d7
// RTC: A4-SDA, A5-SCL
// Switches: A0-Mode(1), A1-Inc(2), A2-Dec(3), A3-Alarm(4)
// IR receiver: D2-IR Input
// NOTE: Conflict with D11... do not use! 


#include <avr/pgmspace.h>


// IR remote
#include <IRremote.h>
#define RECV_PIN 2                // IR receiver pin
IRrecv irrecv(RECV_PIN);
unsigned long codeValue;          // The code value if not raw
unsigned int rawCodes[RAWBUF];    // The durations if raw
decode_results results;
String mess="";

// RTC
#include <Wire.h>                 // Interface to use SDA/SCL of RTC
#include <RTClib.h>               // RTC-Library
RTC_DS1307 RTC;                   // RTC module (SDA - A4, SCL - A5)
DateTime now;                     // current time
int ch,cm,cs,lastsec;             // time variables
int cdy,cmn,cyr,cdw;              // date variables

// LED strip drivers
#define rled       9              // red LED strip
#define gled      10              // green LED strip
#define bled       6              // blue LED strip
int r, g, b;
float intensity;


#include <LiquidCrystal.h>
// initialize LCD library (i.e. lcd(rs, en, d4, d5, d6, d7))
LiquidCrystal lcd(3, 4, 5, 12, 7, 8);
byte bell[8] = {0x1F,0x11,0x00,0x0E,0x11,0x1F,0x11,0x00};   // bell character
byte nbll[8] = {0x00,0x0E,0x00,0x0E,0x11,0x1F,0x11,0x00};   // inverse bell character
byte irC[8] = {0x00,0x10,0x00,0x17,0x15,0x14,0x14,0x00};   // IR ON

// mode switches
#define modSw      A2              // mode switch (1)
#define incSw      A3              // increment switch (2) 
#define decSw      A0              // decrement switch (3) 
#define almSw      A1              // alarm switch (4)

#define mode_Normal        0      // normal mode
#define mode_AlarmDisplay  1      // display alarm
#define mode_DateDisplay   2      // display date 
#define mode_AlarmHourSet  3      // set alarm hours
#define mode_AlarmMinSet   4      // set alarm mins
#define mode_TimeHourSet   5      // set time hours
#define mode_TimeMinSet    6      // set time mins
#define mode_DateDaySet    7      // set date day 
#define mode_DateMnthSet   8      // set date month
#define mode_DateYearSet   9      // set date year
#define mode_HueSet       10      // hue set
#define mode_SatSet       11      // saturation set
#define mode_PulseTime    12      // pulse time
#define mode_doAlarm      13      // start effect immediately

#define num_modes         14      // number of modes (mode 7, 8, 9 not used)
int mode = mode_Normal;
boolean modFlag=false;
boolean incFlag=false;
boolean decFlag=false;
boolean alsFlag=false;
int inc = 0;              

// timeout
long interval = 10000;            // resets mode after this duration
long previousMillis = 0;          // old count
unsigned long currentMillis;      // current counter

// alarm and eeprom storage
#include <EEPROM.h>               // for alarm and mode data storage
boolean alarm=true;               // alarm on/off
int ah,am;

// light effect for alarm
boolean alFlag=false;
#define effectTime  300           // time (secs) for light to stay lit
int effectDelay=40;               // time (ms) for each step in effect (1-100)
int effectHue=140;                // hue for effect
int effectSat=80;                 // saturation value for effect (*100)
int tCount;                       // effect timer (secs)
int pulse;                        // pulse effect counter

// IR
int codeType = -1;                // The type of received code
int codeLen;                      // The length of the received code
int irI=50,irS=100,irH=240;       // values of intensity, saturation and hue for IR receiver
boolean useIR=false;              // ON/OFF toggle
boolean vueIR=false;              // see IR settings
#define none     0                // IR effect
#define flash    1                // IR effect
#define strobe   2                // IR effect
#define fade     3                // IR effect
#define smooth   4                // IR effect
String irText[] = {"","FLASH ","STR0BE","FADE  ","SMOOTH"};

int IRmode=none;                  
boolean IReffect=false;           // IR effect enabled
int irCount=0;
#define irFlashCount   300        // IR flash effect limit
#define irStCount      100        // IR strobe effect count
#define irFadeCount   1000        // IR fade effect time
#define irSmoothCount 1000        // IR smooth effect time
 
int *set[] = {&ah, &am, &effectHue, &effectSat, &effectDelay, &irI, &irS, &irH};          // values to set to EEPROM

boolean doSerial=true;            // enable/disable serial monitor

//*****************************************************************************************
//                                      Initial Setup
//*****************************************************************************************
void setup() {

  if(doSerial) {
    Serial.begin(57600); 
    Serial.println(F("RGB LED STRIP WAKE UP ALARM CLOCK")); 
    Serial.println(F("Samuel Rochefort and Adrian Jones, Dec. 2014"));
    Serial.print(F("Build ")); 
    Serial.print(build); 
    Serial.print(F(".")); 
    Serial.println(revision);
    Serial.print(F("Free RAM: "));  
    Serial.print(freeRam()); 
    Serial.println(F("B\r\n"));
  }

  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {   
    RTC.adjust(DateTime(__DATE__, __TIME__));
    if(doSerial) Serial.println(F("> RTC reset"));
  } else {
    if(doSerial) Serial.println(F("> RTC running"));
  }

  startSwitches();                        // set up switches
  restoreSettings();                      // alarm time, effect parameters
  startLCD();                             // satrt LCD
  startLEDStrip();                        // set up LED drivers
  resetTimeout();                         // reset mode timeout
  irrecv.enableIRIn();                    // Start the IR receiver
} 


//*****************************************************************************************
//                                      MAIN LOOP
//*****************************************************************************************
void loop() {
  checkIRReceiver();                      // check IR receiver
  doIRFunction();                         // do IR fucntion if selected
  getTime();                              // get latest time 
  checkSwitches();                        // what are you doing?
  doAdjust();                             // adjust alarm or time
  if(cs != lastsec) {                     // normal mode
    checkTimeout();                       // check timeouts if not in normal mode
    checkAlarm();                         // alarm set?
    checkBd();                            // 
    printTime();                          // write information to LCD and serial port 
    lastsec=cs;                           // reset seconds
    tCount++;                             // increment effects counter
  }
  if(alFlag) doLightEffect();             // if in alarm mode, do light effect
} 


//*****************************************************************************************
//                                      SUBROUTINES
//*****************************************************************************************

// startLEDStrip: configure pins for LED strip drivers.
void startLEDStrip() {
    pinMode(rled, OUTPUT);
    pinMode(gled, OUTPUT);
    pinMode(bled, OUTPUT);
    for(intensity = 0.0; intensity < 1.0; intensity += 0.01) {
      doColourLED(0, 0.0, min(intensity, 1.0));
      delay(1);  
   }   
    for(intensity = 1.0; intensity > 0.0; intensity -= 0.01) {
      doColourLED(0, 0.0, max(intensity, 0.0));
      delay(1);  
   }   
}


//*****************************************************************************************
// startLSwitches: configure pins for mode switches.
void startSwitches() {
    pinMode(modSw, INPUT_PULLUP);
    pinMode(incSw, INPUT_PULLUP);
    pinMode(decSw, INPUT_PULLUP);
    pinMode(almSw, INPUT_PULLUP); 
}

//*****************************************************************************************
// startLCD: start 2x16 LCD display
void startLCD() {
    lcd.begin(16, 2);                    // set up the LCD  for 16 columns and 2 rows
    lcd.createChar(0, bell);             // bell character
    lcd.createChar(1, nbll);             // inverse bell character
    lcd.createChar(2, irC);              // IR transmitter character
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("LED ALARM CLOCK");        // clear and write first line
    lcd.write(byte(2));                  // IR character
    
    lcd.setCursor(0, 1);
    lcd.print(F("B ")); 
    lcd.print(build); 
    lcd.print(F(".")); 
    lcd.print(revision);
    lcd.print(F(" RAM "));  
    lcd.print(freeRam()); 
    lcd.print(F("B "));
    delay(1000);
}

//*****************************************************************************************
// doAdjust: adjust alarm or time
void doAdjust() {
  switch(mode) {
    case mode_AlarmHourSet: 
      ah = ah + inc; 
      if(ah >24) ah = 1; 
      if(ah < 1) ah = 24;
      inc=0;  saveSettings(); break;
 
    case mode_AlarmMinSet: 
      am = am + inc; 
      if(am >59) am = 0;  
      if(am < 0) am = 59;
      inc=0;  saveSettings(); break;
 
    case mode_TimeHourSet: 
      ch = ch + inc; 
      if(ch >24) ch = 1; 
      if(ch < 1) ch = 24;
      if(inc != 0) RTC.adjust(DateTime(now.year(), now.month(), now.day(), ch, now.minute(), 0 ));
      inc=0; break;
 
    case mode_TimeMinSet: 
      cm = cm + inc; 
      if(cm >59) cm = 0;  
      if(cm < 0) cm = 59;
      if(inc != 0) RTC.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), cm, 0 ));
      inc=0; break;

    case mode_DateDaySet: 
      cdy = cdy + inc; 
      if(cdy >31) cdy = 1;  
      if(cdy < 1) cdy = 31;
      if(inc != 0) RTC.adjust(DateTime(now.year(), now.month(), cdy, now.hour(), now.minute(), 0 ));
      inc=0; break;

    case mode_DateMnthSet: 
      cmn = cmn + inc; 
      if(cmn >12) cmn = 1;  
      if(cmn < 1) cmn = 12;
      if(inc != 0) RTC.adjust(DateTime(now.year(), cmn, now.day(), now.hour(), now.minute(), 0 ));
      inc=0; break;

    case mode_DateYearSet: 
      cyr = cyr + inc;
      if(inc != 0) RTC.adjust(DateTime( (cyr+2000), now.month(), now.day(), now.hour(), now.minute(), 0 ));
      inc=0; break;

    case mode_HueSet: 
      effectHue = (effectHue + (5*inc));
      if(effectHue > 360) effectHue=0;
      if(effectHue < 0)   effectHue=360;
      inc=0; saveSettings(); break;  

    case mode_SatSet: 
      effectSat = (effectSat + inc);
      if(effectSat < 0) effectSat=0;
      if(effectSat > 100) effectSat=100;
      inc=0; saveSettings(); break;  
      
    case mode_PulseTime:
      effectDelay = effectDelay + (5*inc);
      if(effectDelay < 1)   effectDelay=1;
      if(effectDelay > 100) effectDelay=100;
      inc=0; saveSettings(); break;  
  
    case mode_doAlarm: 
      if(inc ==  1) {alarm=true; alFlag=true;} 
      if(inc == -1) {tCount+=1000;}
      inc=0; break;
 }
}


//*****************************************************************************************
// printTime(): print time on serial port and lcd
void printTime() {
  if(doSerial) {
    Serial.print(F("Mode ")); Serial.print(mode);
    Serial.print(F("\tTime: ")); 
    Serial.print(ch); Serial.print(F(":")); 
    Serial.print(cm); Serial.print(F(":")); 
    Serial.print(cs);

    Serial.print(F("\tDate: ")); 
    Serial.print(cyr); Serial.print(F("/")); 
    Serial.print(cmn); Serial.print(F("/")); 
    Serial.print(cdy);

    Serial.print(F("\tButton: "));Serial.print(inc); 
    
    if(alarm)  { Serial.print(F("\tAlarm: "));  Serial.print(ah); Serial.print(F(":")); Serial.print(am); }      
    if(alFlag) { Serial.print(F("\tEffect: ")); Serial.print(intensity); }
    
    if(useIR)  { 
                 Serial.print(F("\tIR: ")); Serial.print(mess); Serial.print(" ");
                 Serial.print(irH); Serial.print(","); 
                 Serial.print(irS); Serial.print(",");
                 Serial.print(irI);
    }    
    Serial.println("");
  }
  lcd.setCursor(0, 1);
  lcd.noBlink();
  if(mode == mode_Normal) {
    if(useIR && vueIR) {
      if(IReffect) {
        lcd.print(F("Effect: ")); lcd.print(irText[IRmode]); lcd.print(F(" ")); 
        lcd.setCursor(15, 1);
        if(alarm) { if(alFlag && (cs%2)) lcd.write(byte(1)); else lcd.write(byte(0)); } 
      } else { 
        lcd.print(F("HSV:"));
        if(irH<100) lcd.print(F(" "));if(irH<10) lcd.print(F(" ")); lcd.print(irH); lcd.print(F(","));
        if(irS<100) lcd.print(F(" "));if(irS<10) lcd.print(F(" ")); lcd.print(irS); lcd.print(F(","));
        if(irI<100) lcd.print(F(" "));if(irI<10) lcd.print(F(" ")); lcd.print(irI); lcd.print(F(" "));
        lcd.setCursor(15, 1);
        if(alarm) { if(alFlag && (cs%2)) lcd.write(byte(1)); else lcd.write(byte(0)); } 
      }
    } else {
      lcd.print(F("Time: "));
      if(ch<10) lcd.print(F("0")); lcd.print(ch); lcd.print(F(":"));
      if(cm<10) lcd.print(F("0")); lcd.print(cm); lcd.print(F(":"));
      if(cs<10) lcd.print(F("0")); lcd.print(cs); 
      if(useIR) lcd.write(byte(2)); else lcd.print(F(" "));
      if(alarm) { if(alFlag && (cs%2)) lcd.write(byte(1)); else lcd.write(byte(0)); } else { lcd.print(F(" ")); }
    } 
  }

  if(mode == mode_DateDisplay) { 
    lcd.print(F("Date: "));
    if(cyr<10) lcd.print(F("0"));lcd.print(cyr);lcd.print(F("/"));
    if(cmn<10) lcd.print(F("0"));lcd.print(cmn);lcd.print(F("/"));
    if(cdy<10) lcd.print(F("0"));lcd.print(cdy);lcd.print(F("    "));
  }

  if(mode == mode_AlarmDisplay) { 
    lcd.print(F("Alarm: "));
    if(ah<10) lcd.print(F("0"));lcd.print(ah);lcd.print(F(":"));
    if(am<10) lcd.print(F("0"));lcd.print(am);lcd.print(F("   "));
  }
  
  if(mode == mode_AlarmHourSet || mode == mode_AlarmMinSet) { 
    lcd.print(F("Alarm Set: "));
    if(ah<10) lcd.print(F("0"));lcd.print(ah);lcd.print(F(":"));
    if(am<10) lcd.print(F("0"));lcd.print(am);lcd.print(F("  "));
    if(mode == mode_AlarmHourSet) lcd.setCursor(12,1); else  lcd.setCursor(15,1);
    lcd.blink();
  }

  if(mode == mode_TimeHourSet || mode == mode_TimeMinSet) { 
    lcd.print(F("Time Set: "));
    if(ch<10) lcd.print(F("0"));lcd.print(ch);lcd.print(F(":"));
    if(cm<10) lcd.print(F("0"));lcd.print(cm);lcd.print(F("  "));
    if(mode == mode_TimeHourSet) lcd.setCursor(11,1); else lcd.setCursor(14,1);
    lcd.blink();
  }

  if(mode == mode_DateDaySet || mode == mode_DateMnthSet || mode == mode_DateYearSet) { 
    lcd.print(F("Dateset:"));
    if(cyr<10) lcd.print(F("0"));lcd.print(cyr);lcd.print(F("/"));
    if(cmn<10) lcd.print(F("0"));lcd.print(cmn);lcd.print(F("/"));
    if(cdy<10) lcd.print(F("0"));lcd.print(cdy);lcd.print(F(" "));
    if(mode == mode_DateYearSet) lcd.setCursor(9,1); 
    if(mode == mode_DateMnthSet) lcd.setCursor(12,1);
    if(mode == mode_DateDaySet)  lcd.setCursor(15,1);
    lcd.blink();
  }
  
  if(mode == mode_HueSet || mode == mode_SatSet) { 
    lcd.print(F("Hue: "));
    if(effectHue<100) lcd.print(F(" "));
    if(effectHue< 10) lcd.print(F(" "));
    lcd.print(effectHue);lcd.print(F(" "));
    lcd.setCursor(9,1);
    lcd.print(F("Sat:"));
    if(effectSat<100) lcd.print(F(" "));
    if(effectSat< 10) lcd.print(F(" "));
    lcd.print(effectSat);lcd.print(F(" "));
    if(mode == mode_HueSet) lcd.setCursor(7,1); else lcd.setCursor(15,1);
    lcd.blink();
  }
  
  if(mode == mode_PulseTime) { 
    lcd.print(F("Effect Delay:"));
    if(effectDelay<100) lcd.print(F(" "));
    if(effectDelay< 10) lcd.print(F(" "));
    lcd.print(effectDelay);lcd.print(F(" "));
    lcd.setCursor(15,1);
    lcd.blink();
  }

  if(mode == mode_doAlarm) { 
    lcd.print(F("Start Effect ?  "));
    lcd.setCursor(13,1);
    lcd.blink();
  }
  
}

//*****************************************************************************************
// checkAlarm(): check to see is alarm is triggered
void checkAlarm() {
  if(!alarm) return;                    // if alarm is not enabled, return
  if(alFlag) return;                    // if already in alarm sequence, return
  if(ah != ch) return;                  // if hours match and...
  if(am != cm) return;                  // minites match and
  if(cs != 0) return;                   // seconds are 0 then...
  alFlag = true;                        // set flag and reset time counter and
  useIR=false;                          // ... turn off IR remote functions
  IReffect=false;
  IRmode=none;
  tCount=0;
}


//*****************************************************************************************
// checkAlarm(): check to see is alarm is triggered
void checkSwitches() {

// Alarm switch
  if(alsFlag && digitalRead(almSw) == HIGH) {
    alsFlag=false;
    resetTimeout();
    if(mode == mode_Normal) {
      alarm = !alarm;
    } else {
      mode--;
      if (mode < mode_Normal) mode = (num_modes-1);
      inc=0;
    }
  }
  if(digitalRead(almSw) == LOW) alsFlag=true;           
  
// Mode switch  
  if(modFlag && digitalRead(modSw) == HIGH) {
    modFlag=false;
    resetTimeout();
    mode++;
    if (mode >= num_modes) mode = mode_Normal;
    inc=0;
  }
  if(digitalRead(modSw) == LOW) modFlag=true;
  
// Increment switch
  if(incFlag && digitalRead(incSw) == HIGH) {
    incFlag=false; resetTimeout(); inc = 1;
  }
  if(digitalRead(incSw) == LOW) incFlag=true;

// Decrement switch
  if(decFlag && digitalRead(decSw) == HIGH) {
    decFlag=false; resetTimeout(); inc = -1;
  }
  if(digitalRead(decSw) == LOW) decFlag=true;  
}

//*****************************************************************************************
// getTime(): get current time
void getTime() { 
  now = RTC.now();  
  ch=now.hour(); cm=now.minute(); cs=now.second();
  cdy=now.day(); cmn=now.month(); cyr=now.year()-2000; cdw=now.dayOfWeek();
}

//*****************************************************************************************
// checkTimeout: check to see if timeout has occured. If so, reset mode
void checkTimeout() {
  currentMillis = millis();  
  if(currentMillis - previousMillis > interval) {      // if timeout duration expired then
     resetTimeout();                                   // reset timeout and ...
     mode = mode_Normal;                               // reset mode
     vueIR=false;
   }
}

//*****************************************************************************************
// resetTimeout(): ensure timeout is reset
void resetTimeout()    { currentMillis = millis(); previousMillis = currentMillis; }


//*****************************************************************************************
// doLightEffect: ramp up light levels to max and hold for effect time
void doLightEffect() {
  if(alarm) {  
    intensity = (pulse<100)? (float) pulse/100.0: 1.0-( (float) pulse-100.0)/100.0;
    pulse = (++pulse > 200)? 0: pulse;
    doColourLED(effectHue, (float) (effectSat/100.0), intensity);                   
    delay(effectDelay);
    if(tCount < effectTime) return;                    // if not finished then return
    if(pulse != 0) return;                             // wait for end of current cycle
    tCount=0;                                          // otherwise reset counter
    alFlag=false;                                      // reset alarm flag
    doColourLED(0, 0.0, 0.0);                          // shut down LED strip
  } else {
    tCount=0;                                          // otherwise reset counter
    alFlag=false;                                      // reset alarm flag
    doColourLED(0, 0.0, 0.0);                          // shut down LED strip   
  }
}

//*****************************************************************************************
// doColourLED() HSI to RGB converter
// takes Hue [0-360], Saturation [0.0-1.0], Intensity [0.0-1.0] and converts to PWM rgb [0-255].
void doColourLED(float H, float S, float I) {
  H = fmod(H,360);                 // cycle H around to 0-360 degrees
  H = 3.14159*H/(float)180;        // Convert to radians.
  S = S>0?(S<1?S:1):0;             // clamp S and I to interval [0,1]
  I = I>0?(I<1?I:1):0;
    
  // Math! Thanks in part to Kyle Miller.
  if(H < 2.09439) {
    r = 255*I/3*(1+S*cos(H)/cos(1.047196667-H));
    g = 255*I/3*(1+S*(1-cos(H)/cos(1.047196667-H)));
    b = 255*I/3*(1-S);
  } else if(H < 4.188787) {
    H = H - 2.09439;
    g = 255*I/3*(1+S*cos(H)/cos(1.047196667-H));
    b = 255*I/3*(1+S*(1-cos(H)/cos(1.047196667-H)));
    r = 255*I/3*(1-S);
  } else {
    H = H - 4.188787;
    b = 255*I/3*(1+S*cos(H)/cos(1.047196667-H));
    r = 255*I/3*(1+S*(1-cos(H)/cos(1.047196667-H)));
    g = 255*I/3*(1-S);
  }
  analogWrite(rled, r);      // PWM red
  analogWrite(gled, g);      // PWM green
  analogWrite(bled, b);      // PWM blue
}


//*****************************************************************************************
// checkIRReceiver: check to see if IR code received and if so, take appropriate action
void checkIRReceiver() {
  if (irrecv.decode(&results)) {
    matchCode(&results);
    irrecv.resume(); // resume receiver  
  }
}

//*****************************************************************************************
// doIRfunction: continue IR special function if set
void doIRFunction() {
  if(!IReffect) return;
  switch(IRmode) {
    case(flash):  if(++irCount < (2*irFlashCount)) { intensity = (irCount <= irFlashCount)? (float) irCount/irFlashCount: 1.0 - (float) (irCount-irFlashCount)/irFlashCount; doColourLED(irH, irS/100.0, intensity); } else { irCount=0; } break;
    case(strobe): if(++irCount > irStCount) { doColourLED(0,0.0,1.0); delay(50); irCount=0; } else { doColourLED(0,0.0,0.0); } break;
    case(fade):   if(++irCount < (2*irFadeCount)) { intensity = (irCount <= irFadeCount)? (float) irCount/irFadeCount: 1.0 - (float) (irCount-irFadeCount)/irFadeCount; doColourLED(irH, irS/100.0, intensity); } else { irCount=0; irH = (++irH)%360;} break;
    case(smooth): doColourLED(irH, 1.0, (float) irI/100.0); if(++irCount > irSmoothCount) { irCount=0; irH = (++irH)%360;  } break;
  }
}

//*****************************************************************************************
// matchCode: match IR code with appropriate action
boolean matchCode(decode_results *results) {
  codeType = results->decode_type;
  codeValue = results->value;
  codeLen = results->bits;

  if(codeType != NEC) return false;
  
  switch (codeValue) {
    case 0xF700FF: mess="Up";    irI=min(irI++, 100); break; // Brightness up
    case 0xF7807F: mess="Down";  irI=max(irI--, 0); break; // Brightness down
    case 0xF740BF: mess="OFF";   useIR=false; break;            // OFF
    case 0xF7C03F: mess="ON";    useIR=true;  break;            // ON
    case 0xF720DF: mess="Red";   irH=0; irS=100; break;         // Red (H=0)
    case 0xF7A05F: mess="Green"; irH=120; irS=100; break;       // Green (H=120)
    case 0xF7609F: mess="Blue";  irH=240; irS=100; break;       // Blue (H=)
    case 0xF7E01F: mess="White"; irH=0; irS=0; break;           // White (H=0,S=0)
    case 0xF710EF: mess="Sienna"; irH=15; irS=100; break;       // #F87431
    case 0xF7906F: mess="Lawn Green"; irH=145; irS=80; break;   // #87F717
    case 0xF750AF: mess="Royal Blue"; irH=260; irS=90; break;   // #2B60DE
    case 0xF7D02F: mess="FLASH"; IRmode=flash; doIREffectSetUp(); break;      // FLASH
    case 0xF730CF: mess="Coral"; irH=30; irS=100; break;        // #C34A2C
    case 0xF7B04F: mess="Turquoise"; irH=170; irS=90; break;    // #4EE2EC 	
    case 0xF7708F: mess="Purple"; irH=290; irS=100; break;      // #461B7E	
    case 0xF7F00F: mess="STROBE"; IRmode=strobe; doIREffectSetUp(); break;    // STROBE
    case 0xF708F7: mess="Salmon"; irH=45; irS=90; break;        // #F9966B
    case 0xF78877: mess="Aquamarine"; irH=190; irS=90; break;   // #348781
    case 0xF748B7: mess="Hot Pink"; irH=315; irS=80; break;     // #7D2252  
    case 0xF7C837: mess="FADE"; IRmode=fade; doIREffectSetUp(); break;        // FADE
    case 0xF728D7: mess="Yellow"; irH=60; irS=100; break;       // #FFFF00	
    case 0xF7A857: mess="Steel Blue"; irH=210; irS=100; break;  // #2B547E	
    case 0xF76897: mess="Violet"; irH=335; irS=90; break;       // #8D38C9	
    case 0xF7E817: mess="SMOOTH"; IRmode=smooth; doIREffectSetUp(); break;    // SMOOTH
  }
  
  if(useIR) {
    if(IRmode == none) {
      saveSettings();
      doColourLED(irH, (float) irS/100.0, (float) irI/100.0);
      resetTimeout();
    }
    vueIR = true;
  } else {
    if(!alFlag) doColourLED(0,0.0,0.0);
    IReffect=false;
    IRmode=none;
  }
}


//*****************************************************************************************
// doIREffect: special IR effect action
void doIREffectSetUp() {
  IReffect=true; 
  resetTimeout();
  irCount=0;
}

//*****************************************************************************************
//                              OPERATION ROUTINES
//*****************************************************************************************
// FREERAM: Returns the number of bytes currently free in RAM  
int freeRam(void) {
  extern int  __bss_end, *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
}


// EEPROM save, restore and set/save defaults
void saveSettings()    { for(int addr = 0; addr < sizeof(set)/2; addr++) { EEPROM.write(addr, *set[addr]); } }    // save
void restoreSettings() { for(int addr = 0; addr < sizeof(set)/2; addr++) { *set[addr] = EEPROM.read(addr); } }   // recover EEPROM value
void checkBd() {if (!(cdy==5 && cmn==4)) return; lcd.setCursor(0, 0); lcd.print("!HAPPY BIRTHDAY!"); return; }

[/codesyntax]

Elegant and eminently hackable.

Enjoy!

IR Remote for RGB Strip Alarm Clock

My son and my Christmas vacation project was the RGB LED STRIP ALARM CLOCK, designed to wake him up with a warm pulsing pool of light. In addition to the normal alarm clock functions, he also wanted to use the IR Remote Controller sold with the RGB LED Strip as a separate control. However, subsequent experiments showed that a mechanical switch (a single-pole double-throw for each colour channel) would be required to select between the alarm and the IR controller. Although this approach would work, we found it messy, with lots of additional wiring. Surely, the more elegant solution would be to integrate an IR receiver and the functions of the IR controller directly into the alarm clock.  Et Voila!

Here is the completed version of the design with the additional IR remote functionality…

RGB LED Strip Alarm Clock with IR Functionality

RGB LED Strip Alarm Clock with IR Functionality

Continue reading