Showing posts with label I2C. Show all posts
Showing posts with label I2C. Show all posts

Monday, September 29, 2025

Plotting Temperature with the ESP32C3 Dev Module and Node Red

  

-----

Microcontrollers are getting really cheap.  They were already cheap, but now they seem crazy cheap.  Even with onboard WiFi, Bluetooth, and a small OLED display we picked up this ESP32C3 Dev Module for about ~$2 USD; so we had to get two.   One turned into an extremely useful and accurate clock while this one will be a temperature logger.

-----

We used a DS18B20 temperature sensor.  The simple connection of the sensor to the ESP32C3 looks like this: 

 
-----
Now you're ready to use the Arduino IDE to upload the software sketch at the end of this post.  Basically the software polls the DS18B20 for a temperature reading every 60 seconds and posts it as a webpage. Our ESP32C3 is connected to our LAN at 192.168.1.67 so we see this in our web browser:
----
But wait, that's not all... We have Node Red running on a Raspberry PI and parse what this web page would look like every 60 seconds to graph the reading.  This isn't a Node Red tutorial, but the flow looks like this and we will post the flow below for you to import.
 

-----

So, what do you get?   A graph like this.  Note that we are charting two temperatures on our chart.  Your chart will only show the ESP32 line: 

 -----

Now for the software code we promised.   Here is the Node Red flow to import:

 [
    {
        "id": "e19a60a2f08ac386",
        "type": "inject",
        "z": "c0bb5756099d6dbc",
        "name": "Every 60 secs",
        "props": [],
        "repeat": "60",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "",
        "x": 160,
        "y": 120,
        "wires": [
            [
                "018abb4b7bfb7e86",
                "41314d52d14f7623"
            ]
        ]
    },
    {
        "id": "41314d52d14f7623",
        "type": "http request",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "http://192.168.1.67/",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 150,
        "y": 180,
        "wires": [
            [
                "2fb863c6f3188fd2"
            ]
        ]
    },
    {
        "id": "2fb863c6f3188fd2",
        "type": "function",
        "z": "c0bb5756099d6dbc",
        "name": "Parse ESP32 Temp",
        "func": "var payload = msg.payload;\nvar match = payload.match(/Temperature is: ([0-9.]+)/);\n\nif (match) {\n    msg.payload = parseFloat(match[1]);  // Fahrenheit\n    msg.topic = \"ESP32\";   // Add this line\n} else {\n    msg.payload = null;\n}\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 180,
        "wires": [
            [
                "ab11f8582b84df82",
                "e024d71190743b50",
                "e146810a6d814e42"
            ]
        ]
    },
    {
        "id": "ab11f8582b84df82",
        "type": "debug",
        "z": "c0bb5756099d6dbc",
        "name": "ESP32 TempF",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 880,
        "y": 180,
        "wires": []
    },
    {
        "id": "e024d71190743b50",
        "type": "ui_gauge",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "group": "7",
        "order": 5,
        "width": 5,
        "height": 4,
        "gtype": "donut",
        "title": "ESP32 (°F)",
        "label": "°F",
        "format": "{{value}}",
        "min": "80",
        "max": "110",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ff0000"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 870,
        "y": 220,
        "wires": []
    },
    {
        "id": "7",
        "type": "ui_group",
        "name": "RasPI-3B",
        "tab": "6",
        "order": 1,
        "disp": true,
        "width": 16,
        "collapse": false,
        "className": ""
    },
    {
        "id": "6",
        "type": "ui_tab",
        "name": "Home",
        "icon": "dashboard",
        "order": 1
    }
]

-----

And here is the Arduino Sketch for the ESP32C3:

// ESP32-C3 Dev Module + onboard OLED 
// thermometer w/ DS18B20 data pin connected to GPIO 4
//
// OLED: Fahrenheit only (1 decimal place, no units)
// Serial Monitor: Celsius + Fahrenheit
// Web page: latest calibrated Fahrenheit reading with timestamp
//
// https://www.whiskeytangohotel.com/
// SEPT 2025

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFi.h>
#include "time.h"
#include <WebServer.h>

// WiFi credentials
const char* ssid     = "ur-ssid";
const char* password = "ur-password";

// OLED setup
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// Global counter
int readingCount = 0;

// DS18B20 setup
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Timezone
const char* ntpServer = "pool.ntp.org";

// Latest reading
float latestTempF = 0;
time_t latestTime = 0;

// Web server
WebServer server(80);

void handleRoot() {
  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);

  String html = "<html><head><title>ESP32-C3 Temp</title></head><body><pre>";
  html += timeBuf;
  html += " - Temperature is: ";
  html += String(latestTempF, 1);
  html += "</pre></body></html>";

  server.send(200, "text/html", html);
}

void setup() {
  // I2C pins for ESP32-C3 OLED dev board
  Wire.begin(5, 6);
  Wire.setClock(100000);
  delay(200);

  u8g2.begin();
  Serial.begin(115200);
  delay(200);

  sensors.begin();

  // Startup screen
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(0, 15, "LAN IP is:");  // Could change this to a "Title Screen"
  u8g2.sendBuffer();
  delay(2000);

  // Connect to WiFi
  WiFi.begin(ssid, password);
  u8g2.clearBuffer();
  u8g2.drawStr(0, 15, "LAN IP is:");
  u8g2.sendBuffer();
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // NTP
  configTzTime("CST6CDT,M3.2.0/2,M11.1.0/2", ntpServer);

  // Start server
  server.on("/", handleRoot);
  server.begin();
  Serial.print("HTTP server started at: ");
  Serial.println(WiFi.localIP());
  //Display last digits of IP address on OLED (.xxx) for easy ID
  String lastOctet = "." + WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.')+1);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fur20_tf);
  int16_t x = (72 - u8g2.getStrWidth(lastOctet.c_str())) / 2;  // center horizontally
  u8g2.drawStr(x, 30, lastOctet.c_str());
  u8g2.sendBuffer();
  delay(5000);
}

void loop() {
  sensors.requestTemperatures();
  float tempC = sensors.getTempCByIndex(0);
  float tempF = tempC * 9.0 / 5.0 + 32.0;
  float calibrationOffsetF = 0.0;
  tempF += calibrationOffsetF;

  // Save latest reading
  time(&latestTime);
  latestTempF = tempF;

  // Serial output
  //readingCount++;  // If reading count is desired
  //Serial.print("#");
  //Serial.print(readingCount);

  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
  Serial.print(timeBuf);

  Serial.print(" > Temperature is: ");
  Serial.print(tempC);
  Serial.print("°C | ");
  Serial.print(tempF);
  Serial.println("°F");

  // OLED output
  char buf[10];
  snprintf(buf, sizeof(buf), "%.1f", tempF);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fur20_tf);
  u8g2.drawStr(0, 30, buf);
  u8g2.sendBuffer();

  server.handleClient(); // handle web requests

  delay(5000); // delay until next reading
}
-----


  

Friday, April 5, 2019

7 Segment 4x4 Random Word Display

-----
This is primarily a variant of the "Four Letter Word Clock" that we modified after getting tired of Daylight Savings Time making us push a few buttons twice a year to correct the time.  Actually, the project is more entertaining now.

The "Four Letter Word Clock" project page provides the BOM and schematics.  We left the Real Time Clock (RTC) implementation in the source code below, but it is not needed as a RTC is not used.

We were able to put 1,002 four letter words into the EPROM,  Since we display two four letter words selected at random that is over a million possible combinations.  The PICAXE random number generator is seeded by doing an analog read on an ADC open pin; basically we read 'noise' and use that for the seed.  It's pretty random, but I don't expect the method to be used in Vegas slot machines
-----
It's interesting to see the combinations produced by the rig.  Bored?  Watch this 4 minute demo:

It's a fun build.
-----
The PICAXE source code is:
#rem
 *******************************
 ***** www.WhiskeyTangoHotel.Com  *****
 *******************************   
    Project Name: 4Letter Word Clock

    Start Date: August 2012
   
    Program Rev History:
       March 2019.  Now called "4Letter Word by 4Letter"
     Tired of the simple two time/year DST change
     so we changed to format to display to random 4 letter
     words side by side.  The words are now random and not sequintial.
   

 *******************************

http://www.dealextreme.com/p/8x-digital-tube-8x-key-8x-double-color-led-module-81873

#endrem

;
;LKM1638 Input pin 3 (CLK)      ---> 18M2 c.0 LEG 17
;LKM1638 Input pin 4 (DIO)      ---> 18M2 c.1 LEG 18
;LKM1638 Input pin 5 (STB0)     ---> 18M2 c.2 LEG 1

;24LC156 EERPOM WP (Write Protect) GND
;24LC156 EERPOM SDA          ----> 18M2 b.1 LEG 7
'24LC156 EERPOM SCL           ----> 18M2 b.4 LEG 10

#picaxe 18m2
#no_data    'do not read internal 18M2 EEPROM

dirsc = 010111        ;c0, c1, c2, c4 as output
symbol clock    = c.0    ;Clock output pin
symbol dio        = c.1    ;Data input output pin
symbol strobe    = c.2    ;Strobe output pin

' s1 thru s8 are the tact swithes under the single RED/Green LEDs
symbol s1        = bit16 ;b2        'to set hours
symbol s2        = bit17 ;b2        ' to set minutes - both to set seconds
symbol s3        = bit18 ;b2
symbol s4        = bit19 ;b2
symbol s5        = bit20 ;b2
symbol s6        = bit21 ;b2
symbol s7        = bit22 ;b2
symbol s8        = bit23 ;b2    'toggle to turn on and off the ticker relay

symbol dataio    = b0 ;w0 and bit 0 to bit 7
symbol pad        = b1 ;w0 and bit 8 to bit 15
symbol iobuf    = w0 ;b0, b1
symbol keys        = b2 ;bit16 to bit 23
symbol fixaddr    = b3 ;start address for DE display

symbol Segment4LEFT    = b4  ;Rightmost 7 seg, LEFT Side
symbol Segment4RIGHT     = b5  ;Rightmost 7 seg, RIGHT Side
symbol Segment2LEFT    = b6  ;Leftmiddle 7 seg, LEFT Side
symbol Segment3LEFT    = b7  ;Rightmiddle 7 seg, LEFT Side
symbol Segment2RIGHT    = b8  ;Leftmiddle 7 seg, Right Side
symbol Segment3RIGHT      = b9  ;Rightmiddle 7 seg, Right Side
symbol Segment1LEFT      = b10 ;Leftmost 7 seg, LEFT Side
symbol Segment1RIGHT    = b11 ;Leftmost 7 seg, Right Side

symbol char        = b12
symbol bank        = b13
symbol tmpry     = b14
symbol dispbrit    = b15
symbol autoaddr    = b16
symbol readmode    = b17
symbol tmpry2    = b18
symbol EEPROMChar = b19
'w10 (b20/21) = used to read var from EEPROM
symbol LEDTicker  = b22

symbol seconds = b23 ' vars for RTC
symbol minutes = b24
symbol hours = b25
symbol blinky = b26 'for RTC 010000 would Enable output at 1Hz blink rate.  000000 is no blink
symbol junkread = b27 'used to read/write RTC day, month, year, date.  Also as a temp var in time set adjust routines

fixaddr        = $c0
dispbrit        = $88    '$88 (136DEC)  min bright.   $8F (143DEC) max bright
autoaddr        = $40
readmode         = $42

init:
high strobe            ;Ensure strobe is initially high
gosub clearchars        ;Clear all characters
blinky = 010000 ' 010000 would Enable output at 1Hz blink rate, start w/ relay click ON..  000000 is no blink.

' Set the time on the DS1307 RTC
i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
pause 50
'\/ \/ \/ \/ Un_REM THESE LINES (BELOW) IF SETTING UP A NEW RTC  \/ \/ \/ \/
#rem
' Set the RTC chip time
;  write time and date e.g. to 11:59:00 on Thurs 25/12/03
'; would be "writei2c 0,($00, $59, $11, $03, $25, $12, $03, 010000)"
' readi2c 0, (b0,b1,b2,b3,b4,b5,b6,b7) reads back the data

let hours = $19        ; 01-12 Note all BCD format
let minutes = $11         ; 00-59 Note all BCD format  
let seconds = $10    ; 00-59 Note all BCD format

; program does not use for we use seconds.  Set manually in the write statement
' for SQ Wave out on RTC.  Last val: 010000 would Enable output at 1Hz blink rate.  000000 is no blink

writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 50

#endrem
'/\ /\ /\ /\ Un_REM THESE LINES (ABOVE) IF SETTING UP A NEW RTC  /\ /\ /\ /\


;--------------------------------------------------------

'I have laid out the 8 segments in the display as:

'| Segment1LEFT | Segment2LEFT | Segment3LEFT | Segment4LEFT | Segment1RIGHT | Segment2RIGHT | Segment3RIGHT | Segment4RIGHT

    ;Segment Values                0-9   = ( 0 , 1,  2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
    '                              10-19 =   A , b , C , d , E , F , g,  H, i,  J,
    '                              20-29 =   K,  L,  M,  N,  o,  P,  q,  r, S,  T, 
    '                              30-35 =   U, V, W,  X,  y,   Z ,
    '                              36-44 =   segA, segB, segC, segD, segE, segF, segG, dp, off)

'the 'gosub display' routine expect 8 values; SegmentxLEFT and SEGMENTxRIGHT coded as
'lookup values shown in the rem above.

' At Startup turn the clicky relay on.  S8 button will turn it off
blinky = 010000
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)

main:

if s1 = 1 or s2 = 1 or s8 = 1 then 'setting the clock time or relay ticker
    if s1 = 1 and s2 = 0 then 'setting hours
        junkread = junkread + 1
        if junkread > 23 then
            junkread = 0
        end if
        lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23), hours
         i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif ' s1 = 1, setting hours
   
    if s2 = 1 and s1 = 0 then 'setting minutes
        junkread = junkread + 1
        if junkread > 59 then
            junkread = 0
        end if
        lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59), minutes
         i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif 's2 = 1, setting minute
   
    if s1 = 1 and s2 = 1 then 'reset seconds to 00
        seconds = $00
        i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif 'settin seconds to zero

    if s8 = 1 then ' turn on/off the clicking relay
        'read the RTC to dected the seconds for the write to RTC below keeps them accurate
        i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
        readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
        pause 10
        if blinky = 010000 then 'blinky from RTC is ON and clinking the relay. turn it OFF
            blinky = 000000
        else              'blinky from RTC is OFF and NOT clinking the relay. turn it ON
            blinky = 010000
        end if
        i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 500
    end if
   

else ' not settign the clock, check for brightness adjust and run as normal; so read a new 4letter word
   
    if s7 = 1 then 'increase brightness
        dispbrit = 140 'other values cause random LED7 behavior
    end if

    if s6 = 1 then 'decrease brightness
        dispbrit = 136   ' 136 is min bright
    end if   

sertxd (#dispbrit, 13,10)
   
    'Get SegmentxLEFT values for clock by reading the RTC
    i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
    readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
    pause 10
    gosub ReadEEPROM  ' read the four letter word.  These are loaded into SegmentxRIGHT vars
    gosub Ticker    'ticks thru the R/G LEDs to show seconds
endif

' THIS IS WHERE SEGMENT LEFT IS LOADED WITH THE TIME.
' CHANGE IT TO A WORD
'
'Segment1LEFT = hours & %11110000 / 16  'BCD so shift upper 4 bits to lower 4 bits
'Segment2LEFT = hours & 001111

'Segment3LEFT = minutes & %11110000 / 16   'BCD so shift upper 4 bits to lower 4 bits
'Segment4LEFT = minutes & 001111

gosub display   'Put the SegmentxLEFT and SEGMENTxRIGHT characters onto the 7 seg displays.

gosub getkeys        ;Read tact buttons
           
goto main

'-------------------------------------------------------
Ticker: 'ticks thru the R/G LEDs to show seconds by cycling through each LED address

junkread = seconds & %11110000
junkread = junkread / 16 * 10
seconds = seconds & 001111
seconds = junkread + seconds

lookup seconds, (1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,5,5,5,5,5,5,5,5,7,7,7,7,7,7,7,7,9,9,9,9,9,9,9,9,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,15,15,15,15), dataio

dataio = dataio + fixaddr    ;LEDs are at odd addresses 1 to 15
junkread = dataio   'used to turn off LED later in this sub
low strobe
gosub sendchar

LEDTicker = LEDTicker + 1

if LEDTicker = 2 then
    LEDTicker = 1
end if

dataio = LEDTicker  'Light the LEDs.  1 = RED.  2 = GREEN.  3 = R/G
gosub sendchar
high strobe

'Turn off LED here
dataio = junkread   
low strobe
gosub sendchar
dataio = 0   '0 turns off the currently selected LED
gosub sendchar
high strobe;

dataio    = dispbrit        ;Display control on, brightness level
low strobe                 ;Strobe low
gosub sendchar
high strobe                ;Strobe high

return 'Ticker

ReadEEPROM:
'24LC256 EEPROM is loaded with 987 four letters words (3948 characters)
'Each character is an address from 0 to 3947
'readi2c addrs, (charvalue)

i2cslave %10100000, i2cslow, i2cword    ; set PICAXE as master and DS1307 slave address

'Read and Translate the char read from the EEPROM for the lookup(.,...), dataio command.
'Read the EEPROM letter then subtract 87 from that ASCII value for the "lookupchar" sub.  Examples:
'ASCII value for a = 97; Lookup in this program value is 10.  So, 97 - 87 = 10
'ASCII value for j = 106; Lookup in this program value is 19.  So, 106 - 87 = 19
'ASCII value for k = 122; Lookup in this program value is 35.  So, 122 - 87 = 35

'Last word in EEPROM is YURT and starts at Location 4008

touch16 B.7, b20 'lower bits w10
touch16 B.7, b21 'w10 upper
'RANDOM number for w10
w12 = w10 // 1003  ; scale it to 0-1002 (4 * 4008 = 4008)
w10 = w12 * 4  ; max is YURT at 4008 start

readi2c w10, (Segment1LEFT)
Segment1LEFT = Segment1LEFT - 87

w10 = w10 + 1
readi2c w10, (Segment2LEFT)
Segment2LEFT = Segment2LEFT - 87

w10 = w10 + 1
readi2c w10, (Segment3LEFT)
Segment3LEFT = Segment3LEFT - 87

w10 = w10 + 1
readi2c w10, (Segment4LEFT)
Segment4LEFT = Segment4LEFT - 87
'
'
'
touch16 B.7, b20 'lower bits w10
touch16 B.7, b21 'w10 upper
'RANDOM number for w10  
w12 = w10 // 1003  ; scale it to 0-1002 (4 * 4008 = 4008)
w10 = w12 * 4  ; max is YURT at 4008 start

readi2c w10, (Segment1RIGHT)
Segment1RIGHT = Segment1RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment2RIGHT)
Segment2RIGHT = Segment2RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment3RIGHT)
Segment3RIGHT = Segment3RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment4RIGHT)
Segment4RIGHT = Segment4RIGHT - 87

pause 1500 'keep the secs LED on and slow down the words

return ' ReadEEPROM


;--------------------------------------------------------

display:    ;Displays data on the 7 seg displays, using 2 blocks of 4 digits

    bank = 0   ;LEFT Side: First block of digits

    dataio    = fixaddr + bank + 0 ;Set Leftmost 7 seg, LEFT Side write address
    low strobe                 ;Strobe low
    gosub sendchar
    char = Segment1LEFT        ;Leftmost 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe                ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, LEFT Side write address
    low strobe                 ;Strobe low
    gosub sendchar
    char = Segment2LEFT        ;Leftmiddle 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe                ; End of data - Strobe high
   
    dataio    = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, LEFT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment3LEFT    ;Rightmiddle 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 6 ;Set Rightmost 7 seg, LEFT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment4LEFT    ;Rightmost 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    'RIGHT BANK
    bank = 8  ;RIGHT Side: Second block of 4 digits
    dataio    = fixaddr + bank + 0 ;Set Leftmost 7 seg, Right Side write address
    low strobe             ;Strobe low
    gosub sendchar
    char = Segment1RIGHT    ;Leftmost 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, Right Side write address
    low strobe             ;Strobe low
    gosub sendchar
    char = Segment2RIGHT    ;Leftmiddle 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, Right Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment3RIGHT    ;Rightmiddle 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ; End of data - Strobe high
   
    dataio    = fixaddr + bank + 6 ;Set Rightmost 7 seg, RIGHT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment4RIGHT    ;Rightmost 7 seg, RIGHT Side
    gosub lookupchar
    gosub sendchar   
   
    '-----------------
   
    'must refresh dispbrit each time
    dataio    = dispbrit ;Display brightness level. $88 (136DEC)  min bright.   $8F (143DEC) max bright
    low strobe         ; Strobe low
    gosub sendchar
    high strobe        ; Strobe high

return   'display

;--------------------------------------------------------

clearchars:            ;Clear LEDs and 7 seg displays.  ALL LEDS OFF. Segs and LEDs
    dataio    = autoaddr ; Data mode auto increment
    low strobe         ; Strobe low
    gosub sendchar
    high strobe        ; Strobe high
    ;
    low strobe         ; Strobe low
    dataio    = fixaddr ; Set start address
    gosub sendchar
    for tmpry = 1 to $0f    ;$0F = 15, so loop runs 16 times.  7 LEDs and 7 seg displays
        dataio = 0        ;Zero blanks the  display
        gosub sendchar
    next
    high strobe            ;Strobe high, keep low to end of data
return

;--------------------------------------------------------

sendchar:    ;Routine to send all characters to LKM1638 module serially
    pad        = $ff    ;$FF = 255.  Set counter
    high clock        ;Ensure clock is high for pulseout
    do
      pinc.1 = bit0    ;Make c.1 the value in bit0
      iobuf = iobuf/2    ;Shift right
      pulsout clock,1 '10us clock pulse
    loop Until pad = 0  'excecute 256 times
return

;--------------------------------------------------------

getkeys:    ;Reads the input tact buttons in and places them in bits16 to bits23
dataio    = readmode    ; Data mode read
low strobe
gosub sendchar
input c.1            ;set c.1 as input
high clock            ;Ensure clock is high for pulseout
for tmpry = 1 to 16    ;Read in bits 0-15
    bit0 = pinc.1    ;Make bit0 the value on c.1. Need to use c.1 as it is both in & out
    iobuf = iobuf*2    ;Shift bit left
    pulsout clock,1    ;10us clock pulse, read next bit
next
s6 = bit3            ;Move 1st word switch values out of buffer
s2 = bit7
s5 = bit11
s1 = bit15
for tmpry = 1 to 16    ;Read in bits 16-31
    bit0 = pinc.1    ;Make bit0 the value on b.0. Need to use c.1 as it is both in & out
    iobuf = iobuf*2    ;Shift bit left
    pulsout clock,1    ;10us clock pulse, read next bit
next
s8 = bit3            ;Move 2nd word switch values out of buffer
s4 = bit7
s7 = bit11
s3 = bit15   
output c.1            ;Return c.1 to output
high strobe
return

;--------------------------------------------------------

lookupchar:    ;Looks up the code to display the digit in 'char' on the 7 seg display
    ;character  0-9   =    ( 0 , 1,  2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
    '               10-19 =         A , b , C , d , E ,  F , g,  H, i,   J,
    '               20-29 =         K,  L,  M,  N, o,   P,  q,  r,  S,  T, 
    '               30-35 =         U, V, W,  X,  y,   Z ,
    '               36-44 =        segA, segB, segC, segD, segE, segF, segG, dp, off)

    lookup char,(63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113,111,118,16,30,118,56,21,84,92,115,103,80,109,120,62,28,42,118,110,91,1,2,4,8,16,32,64,128,0),dataio

return

;------------------------

Sunday, December 18, 2016

Low Cost Bi-Directional Level Shift Module Characterization (TE291)

-----
From time to time the level shift module pictured above has come in handy.  They do a great job shifting 3.3VDC to 5VDC logic or 5VDC logic to 3.3VDC logic.  The cost is about $1USD and hookup is simple.  Of course they are designed for low speed digital signals but, we wondered how the module would handle higher speeds.
----
The Bench Setup:
Keithley 2230G-30-1 power supply for the 3.3VDC and 5VDC power.









Tektronix AFG3252C function generator for the square wave (3.3V and 5V logic) stimulus.










Tektronix MDO4104C oscilloscope to capture the input/output signals.

-----
The Result:
Performance was very good, especially considering these module are often used in the 100KHz and below range.  Leveling from 5V to 3.3V had better results.  The signals start looking ridiculous over 1MHz. Take a look at the scope shots below.
-----
3.3V Level Shifted to 5V at 10KHz, 100KHz, 500KHz, and 1MHz
(Yellow = Input; Blue = Output)




-----
 5V Level Shifted to 3.3V at 10KHz, 100KHz, 500KHz, and 1MHz
(Yellow = Input; Blue = Output)




 -----

Thursday, December 1, 2016

Portable ESP8266 WiFi Sniffer (Arduino IDE)

-----
The ESP8266 modules are so cheap who could resist experimenting with one.  For about $8USD a few things you get are 9 GPIOs, I2C/SPI support, an ADC, and on board WiFi or other goodies.  In addition it's all programmable in the Arduino IDE that is familiar to many and has a good user support network.

This example application shows a quick and easy way to get a portable 'WarDriver' with the WiFi ESP8266 and an OLED display.

No resistors, etc. needed; connect it up like this:
https://www.adafruit.com/product/2821https://www.amazon.com/Diymall-Yellow-Serial-Arduino-Display/dp/B00O2LLT30/ref=sr_1_1?ie=UTF8&qid=1480612090&sr=8-1&keywords=diymall+oled

    Pinout Connections
ESP8266                  OLED
3VDC <<==+==>> Vcc
  GND <<==+==>> GND
 SCL(5) <<==+==>> SCL
 SDA(4) <<==+==>> SDA



Take a look at the source code below for the links on installing ESP8266 capability to the Arduino IDE.  Chances are if you are reading this you already have the Arduino IDE installed; just make sure you are running at least Rev 1.6.8.  Then upload the source code to the ESP8266 and your up.
-----
Here is a sample of the rig running in a random parking lot a fair distance from an apartment complex.  Eight networks were found; all encrypted.  The OLED displays the number of networks, SSID name, signal strength (dBm), and if the network is OPEN or Encrypted.
One thing that was a surprise is how many cars have OPEN WiFi running.  Also, pretty much every long haul 18 wheeler heading down the interstate is a rolling WiFi hotspot, but most (not all) are Encrypted.
-----
Here is the source code to push into the ESP8266 via the Arduino IDE:

/*
 * WhiskeyTangoHotel.Com /  NOV2016
 * 'WarDriver' ESP8266 Adafruit HUZZAH w/ WiFi and 32 line OLED
 *   
 *   Scan WiFi networks leverages from:
 *   https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiScan/WiFiScan.ino
 *   
 *   OLED Driver: Thanks, adafruit.com
 *   
 *   Compile for 80MHz with Arduino IDE
 *      Arduino IDE 1.6.8 or greater
 *      http://www.arduino.cc/en/Main/Software
 *      
 *      ESP8266 Board Package
 *      Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manage
 *      Restart, select your ESP8266 from the Tools->Board dropdown
 *   
 */
#include "ESP8266WiFi.h"  // API for the ESP8266 WiFi

// Setup the 32 line OLED 
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET LED_BUILTIN  // 4
Adafruit_SSD1306 display(OLED_RESET);

#if (SSD1306_LCDHEIGHT != 32)  // change to 64 for larger OLED. See error trap next line.
#error("Height incorrect, please fix Adafruit_SSD1306.h!"); 
#endif

int screen_roll_delay = 800; // How long to leave Network info on OLED. Delay is executed four times (for LED blink)

void setup() {
  // Setup and test writes to the OLED and Serial Monitor (ESP8266 expects Serial Monitor at 115200 baud)
  
  // Some more OLED setup
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();    // Clear the buffer.
  display.display();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  
  display.setCursor(0,0);
  display.println("2.4GHz WiFi Scanner");
  Serial.begin(115200); // Display to serial monitor as well as OLED
  Serial.println("Setup begins....");

  // Set WiFi to station mode and disconnect from an AP if it was previously connected
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);

  Serial.println("Setup completes!!!");
  display.println("------------------");
  display.println("WhiskeyTangoHotel");
  display.println("      .Com");
  display.display();
  delay(5000);  // Welcome/Test screen delay.
}  // end void setup

void loop() {
  Serial.println("Scan starts...");

  // update OLED
  display.clearDisplay();
  display.display();
  display.setCursor(0,0);
  display.println("Scanning...");
  display.display();

  // WiFi.scanNetworks will return the number of networks found as variable "n"
  digitalWrite(0, HIGH);  // On board LED ON
  int n = WiFi.scanNetworks();
  Serial.println(" and completes!!!");
  
  if (n == 0) {  // No WiFi found.  Update the Serial Monitor and the OLED
    Serial.println("No WiFi found!!!");

    // update OLED
    display.clearDisplay();
    display.display();
    display.setCursor(0,0);
    display.println("Scanning...");
    display.println("No WiFi found!!!");
    display.display();
    digitalWrite(0, LOW);  // On board LED OFF
  }      // endif n=0 (no wifi found)
  else  // wifi found
  {
    Serial.print(n);
    Serial.println(" Networks found:");
    Serial.println("----------------");

    for (int i = 0; i < n; ++i)
    {
      digitalWrite(0, HIGH);  // On board LED ON
      
      // Print SSID and RSSI for each network found to Serial Monitor. Show SSID, Signal strenght, and OPEN or Encrypted
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i));
      Serial.print(": ");
      Serial.print(WiFi.RSSI(i));
      Serial.print("dBm | ");
      Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?"Not Encrypted":"Encrypted");

      //update OLED with found WiFi. Show SSID, Signal strenght, and OPEN or Encrypted
      display.clearDisplay();
      display.display();
      display.setCursor(0,0);
      display.print("Network ");
      display.print(i+1);
      display.print(" of ");
      display.println(n);
      display.display();
      display.println(WiFi.SSID(i));
      display.println("-------------------");
      display.print(WiFi.RSSI(i));
      display.print("dBm | ");
      display.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?"OPEN":"Encrypted");
      display.display();

      delay(screen_roll_delay);  // Little delay to allow time to read OLED. Flash the on board LED just for fun.
      digitalWrite(0, LOW);   // On board LED OFF
      delay(screen_roll_delay); 
      digitalWrite(0, HIGH);   // On board LED ON
      delay(screen_roll_delay);  
      digitalWrite(0, LOW);   // On board LED OFF
      delay(screen_roll_delay); 
    }  // end for/next loop for n# of wifi networks found
  }   // endif wifi found (n was <> 0)
  Serial.println("");
}  // end void loop (endless)
-----
Thanks for the visit.

Tuesday, October 25, 2016

Prime Numbers in a Box

-----
How many times have you needed the next prime number in a sequence and, like some animal, had to go to a printed table to look it up.  Well, those days are over.
----
A prime number is any positive whole number that can only get evenly divided only by 1 and itself.  Primes are used in many applications; a popular use being for encryption and cryptography.  Demonstrated here is another use for prime numbers.  That is making use of an older/slower Raspberry PI and a few parts to nerd up the decor of any room.

A few Raspberry PI skills learned will be:
     - writing to text files
     - reading from text files
     - driving a low cost I2C LCD display
     - driving a relay via a transistor
     - simple graceful shutdown method for the RasPI with a button and a JST connector.
-----
 
The project has an entertaining audio effect if you are into numbers.  Primes go on forever and ever; infinitely large.  The smallest numerical difference between two primes is 2 (example: 7-5=2).  What is interesting is the distance (difference) between two consecutive primes stays relatively low as the primes become very large.  Press a button and Primes in a Box gives a audible (relay click) signal for each non prime as it waits to display the next found prime.  If you enjoy mathematics you may find this oddly relaxing.
-----
The python source is pretty straight forward.  On button press a pointer to a file containing the first few million primes is indexed and displayed on the LCD.  A 5VDC relay clicks to represent the non primes in between.  The rig runs via USB power and the last found prime is always saved.  A handy 'shutdown' button is incorporated to allow the Raspbery PI project to be powered down gracefully if it needs to be moved.
-----
You'll need a Raspberry PI, 5VDC relay, PN2222A transistor, 16x2 I2C LCD, two resistors, and two normally open button switches.  A project box holds it all together.  Connect it all up like this:
Once on the breadboard it will look a bit like this:
 -----
Set up the RasPI to run the python code below at reboot (use @reboot in the sudo crontab).
Note, two files are expected to be found in the working directory:
     - prime_list.txt (List of prime numbers in order. One per line. As many as you like)
     - high_prime.txt (Holds the highest prime found in case of a restart/reboot)

#!/usr/bin/python

#
#  WhiskeyTangoHotel.Com
#  OCT 2016
#  
#  Program reads a file and displays prime # on LCD
#  On button press find next prime #.  Click a relay for each non prime
#  Write new highest prime to file to save if restarting the program
#
#  Gives an audible (relay click) representation of the distance between primes
#
#  Expects program to be in: /home/pi/RasPI/Programs-RasPI/Prime_Relay/
#
#  Leverages LCD script using I2C backpack for 16x2 and 20x4 screens.
#  Thanks to Matt Hawkins  http://www.raspberrypi-spy.co.uk/
#
#--------------------------------------

import smbus  # for LCD I2C display
import time # fpr sleep and pause delays
import os # this is for the shutdown button press
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD) # to use Raspberry Pi board pin numbers
# set up GPIO output channel
Relay_Pin = 11 # pin to drive the relay clicks via 2222a transistor
GPIO.setup(Relay_Pin, GPIO.OUT)
Relay_delay = .1  # time delay between relay clicks

Switch = 8  # Button switch to move to next prime number
GPIO.setup(Switch, GPIO.IN)

reboot_pin = 26 #  Push this button and the RasPI shuts down gracefully
#Set pin to input and set pull-up resistor to hold the pin is high
GPIO.setup(reboot_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#######################################
### LCD DRIVER FUNCTIONS START HERE ###
#######################################

# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address
LCD_WIDTH = 16   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable bit

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1 RasPI(Bob)

def lcd_init():
 # Initialise display
 lcd_byte(0x33,LCD_CMD) # 110011 Initialise
 lcd_byte(0x32,LCD_CMD) # 110010 Initialise
 lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
 lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
 lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
 lcd_byte(0x01,LCD_CMD) # 000001 Clear display
 time.sleep(E_DELAY)

def lcd_byte(bits, mode):
 # Send byte to data pins
 # bits = the data
 # mode = 1 for data
 #        0 for command

 bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
 bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

 # High bits
 bus.write_byte(I2C_ADDR, bits_high)
 lcd_toggle_enable(bits_high)

 # Low bits
 bus.write_byte(I2C_ADDR, bits_low)
 lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
 # Toggle enable
 time.sleep(E_DELAY)
 bus.write_byte(I2C_ADDR, (bits | ENABLE))
 time.sleep(E_PULSE)
 bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
 time.sleep(E_DELAY)

def lcd_string(message,line):
 # Send string to display
 message = message.ljust(LCD_WIDTH," ")
 lcd_byte(line, LCD_CMD)
 for i in range(LCD_WIDTH):
  lcd_byte(ord(message[i]),LCD_CHR)
  
#######################################
### LCD DRIVER FUNCTIONS END HERE  ###
#######################################

# Highest calulated prime is stored in a file.  Get that number
highprime = open('/home/pi/RasPI/Programs-RasPI/Prime_Relay/high_prime.txt','r')
storedprime = (highprime.readline())
storedprime_val = int(storedprime)
print ' '
print "Recalled highest prime from file is: " + storedprime
print ' '

#Put status on LCD
lcd_init()
lcd_string("Init to last",LCD_LINE_1)
lcd_string("prime: " + str(storedprime_val),LCD_LINE_2)
highprime.close

def main():
 # Main program block 
 
 # Relay test clicks
 for i in range(1, 3):  
  GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
  time.sleep(Relay_delay) 
  GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
  time.sleep(Relay_delay)  
 
 # Set the pointer to the correct place in the file that holds the prime list
 # prime_list.txt contains primes to 1,299,709
 primefile = open('/home/pi/RasPI/Programs-RasPI/Prime_Relay/prime_list.txt','r')
 currentprime_val = 0

 while storedprime_val > currentprime_val:
  currentprime = (primefile.readline())
  currentprime_val = int(currentprime)
  
 # Update the LCD
 lcd_string("PRESS for next",LCD_LINE_1)
 lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)
 time.sleep(0.5)
 
 # Relay test clicks
 for i in range(1, 3): 
  GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
  time.sleep(Relay_delay) 
  GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
  time.sleep(Relay_delay) 
 
 while True:
  
  if (GPIO.input(reboot_pin) == 0):
   # Update the LCD
   lcd_string("Primes in a Box",LCD_LINE_1)
   lcd_string("shutting down!!!" + str(currentprime_val),LCD_LINE_2)
   time.sleep(5)
   #Send command to system to shutdown
   os.system("sudo shutdown -h now")
  
  if (GPIO.input(Switch) == 0):
   # Update the LCD
   lcd_string("Calculating next",LCD_LINE_1)
   lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)
  
   oldprime = currentprime_val
   currentprime = (primefile.readline())
   currentprime_val = int(currentprime)
   deltaprime = currentprime_val - oldprime 
   
   for i in range(1, deltaprime+1):  # click relay for each non prime between primes
    #print i
    GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
    time.sleep(Relay_delay) 
    GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
    time.sleep(Relay_delay)   

   #Write the prime to a file so it can be recalled at prog start
   hp = open("/home/pi/RasPI/Programs-RasPI/Prime_Relay/high_prime.txt","w")
   hp.write(str(currentprime_val))
   
   print "CURRENT " +  str(currentprime_val)
   print "    OLD " + str(oldprime)
   print '------- '
   print "  DELTA " +  str(deltaprime)
   #print i
   print ' '

   # Update the LCD
   lcd_string("PRESS for next",LCD_LINE_1)
   lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)

   hp.close
   time.sleep(.250)

if __name__ == '__main__':

 try:
  main()
 except KeyboardInterrupt:
  pass
 finally:
  lcd_byte(0x01, LCD_CMD)
----
Thanks for the visit and happy prime numbering!

Sunday, April 10, 2016

Graphing Current Drain of the Sparkfun BadgerHack

-----
Sparkfun had a nice booth at SXSW in Austin that let you build and walk away with their BadgerHack project.  Very generous and Thanks!  Also at the booth were some of the 'moviestars' often seen on their YouTube channel.  It was great to meet a few of them.

I was curious about the current drain of all those LEDs dancing on the display.  I had access to a Keithley 2461 SourceMeter/SMU which makes it easy to run the experiment.  The video below tells it all. 

-----
Thanks again, Sparkfun!

Wednesday, August 29, 2012

Four Letter Word Clock via uC

Why build a clock that displays four letter words?  The current time is everywhere; on your PC, smartphone, GPS, MP3 player, etc.  Heck, you may even own a watch!  Four letter words are pretty common as well.
-----
So why then?
     1st)  I had this display that I bought from DealExtreme.Com.  The only reason I got it was because it was so cheap.

     2nd) I wanted to experiment with writing/reading data from an EEPROM with a microcontroller.

     3rd) I wanted to experiment with controlling two devices on the I2C bus in one application.

But.... I wanted a fun project idea to make the effort seem somewhat worthwhile and settled on a Four Letter Word Clock.

If you you just want to see the results and are not interested in the build details, here is a short video demo.

The time is shown in 24 hour format on the left four 7-segments.  Every second a different four letter word is shown on the right four 7-segments.  The eight LEDs under the 7-segments progress from left to right as a way to display seconds.  If you listen closely to the video and you can hear a relay that gives the clock a mechanical ticking sound. 

 The buttons under the LEDs are used to set hours (S1), minutes (S2), increase display brightness (S6), decrease display brightness (S7), and turn ON/OFF the mechanical ticking sound (S8) from the relay.

The major components of the build are (full schematic to follow):
     - Eight x 7-Segment + 8 x Red/Green LED + 8 x Input Button Display Module
     - 24LC256 EEPROM to store the 1,003 four letter words
     - DS1307 Real Time Clock (RTC) for time keeping
     - Small relay to provide a clock like, mechanical ticking sound
     - PICAXE 18M2 microcontroller with custom code provides the brains
------

The display from DealExtreme.Com is pretty awesome for the price.  It contains eight 7-segment LED displays, eight LEDs that can be red, green, or red/green, and eight button switches.  The display has a solid, well built feel to it and was a bargain at $4.99.  As a plus, you can control all these feature with only three I/O pins on a microcontroller.  On the downside, it ships with no documentation (zero, zip, nada...) so plan on doing some web searching to understand it.
-----

The 24LC256 EEPROM, DS1307 RTC, and PICAXE 18M2 are easy to get from many web sources.  I rescued the Teladyne 712-5 relay from a trash bound PCB.  A good thing because a web search shows that relay at $28 (it's an RF spec relay!).  No fear, you can leave the relay off or just use any cheap relay as it is not used to switch any current, just for the ticking sound.
-----
Now came the time to load the 24LC256 EEPROM with four letter words.  So... to the internet for a quickie download of all the four letter English words (including all your favorite cuss words) in one tight ASCII text file.  Unfortunately, 7-segment displays don't display letters like "K", "M", "V", "W", "X", and "Z" very readable.  I wrote a short Python script to pull out the offenders, which also meant some of the more 'expressive' words where lost.  After it was all done, there were 1,003 four letters words that easily fit into the 24LC256 EEPROM.  A short (and separate) program was written to tell the PICAXE 18M2 to load these words into the 24LC256 EEPROM.
-----
The harder part was the code to drive the display.  The lack of documentation made it pretty challenging.  I always find the PICAXE forum helpful in these situations (special thanks to "mjy58").  After much coding/debugging, the problem was solved. 

Controlling the two I2C devices (the 24LC256 EEPROM and the DS1307 RTC) from the PICAXE 18M2 was a bit easier than I expected after sorting through the addressing procedures.
----
Here is a short vid (time lapse) of the rig working on the AXE091 development board.  In the vid you can see the eight red LEDs progress from left to right as the seconds tick by.


-----
After verifying the operation for a few days the whole mess was moved off the AXE091 development board and onto a strip board PCB.  Installing the rig into a $3.49 metal project box from Radio Shack provided a clean finished product.
 -----
Below is the build schematic (click to enlarge).  
-----
PICAXE source code:
#rem
 *******************************
 ***** www.WhiskeyTangoHotel.Com  *****
 *******************************   
    Project Name: 4Letter Word Clock

    Start Date: August 2012
   
    Program Rev History:


 *******************************

http://www.dealextreme.com/p/8x-digital-tube-8x-key-8x-double-color-led-module-81873

#endrem

;
;LKM1638 Input pin 3 (CLK)      ---> 18M2 c.0 LEG 17
;LKM1638 Input pin 4 (DIO)      ---> 18M2 c.1 LEG 18
;LKM1638 Input pin 5 (STB0)     ---> 18M2 c.2 LEG 1

;24LC156 EERPOM WP (Write Protect) GND
;24LC156 EERPOM SDA          ----> 18M2 b.1 LEG 7
'24LC156 EERPOM SCL           ----> 18M2 b.4 LEG 10

#picaxe 18m2
#no_data    'do not read internal 18M2 EEPROM

dirsc = 010111        ;c0, c1, c2, c4 as output
symbol clock    = c.0    ;Clock output pin
symbol dio        = c.1    ;Data input output pin
symbol strobe    = c.2    ;Strobe output pin

' s1 thru s8 are the tact swithes under the single RED/Green LEDs
symbol s1        = bit16 ;b2        'to set hours
symbol s2        = bit17 ;b2        ' to set minutes - both to set seconds
symbol s3        = bit18 ;b2
symbol s4        = bit19 ;b2
symbol s5        = bit20 ;b2
symbol s6        = bit21 ;b2
symbol s7        = bit22 ;b2
symbol s8        = bit23 ;b2    'toggle to turn on and off the ticker relay

symbol dataio    = b0 ;w0 and bit 0 to bit 7
symbol pad        = b1 ;w0 and bit 8 to bit 15
symbol iobuf    = w0 ;b0, b1
symbol keys        = b2 ;bit16 to bit 23
symbol fixaddr    = b3 ;start address for DE display

symbol Segment4LEFT    = b4  ;Rightmost 7 seg, LEFT Side
symbol Segment4RIGHT     = b5  ;Rightmost 7 seg, RIGHT Side
symbol Segment2LEFT    = b6  ;Leftmiddle 7 seg, LEFT Side
symbol Segment3LEFT    = b7  ;Rightmiddle 7 seg, LEFT Side
symbol Segment2RIGHT    = b8  ;Leftmiddle 7 seg, Right Side
symbol Segment3RIGHT      = b9  ;Rightmiddle 7 seg, Right Side
symbol Segment1LEFT      = b10 ;Leftmost 7 seg, LEFT Side
symbol Segment1RIGHT    = b11 ;Leftmost 7 seg, Right Side

symbol char        = b12
symbol bank        = b13
symbol tmpry     = b14
symbol dispbrit    = b15
symbol autoaddr    = b16
symbol readmode    = b17
symbol tmpry2    = b18
symbol EEPROMChar = b19
'w10 (b20/21) = used to read var from EEPROM
symbol LEDTicker  = b22

symbol seconds = b23 ' vars for RTC
symbol minutes = b24
symbol hours = b25
symbol blinky = b26 'for RTC 010000 would Enable output at 1Hz blink rate.  000000 is no blink
symbol junkread = b27 'used to read/write RTC day, month, year, date.  Also as a temp var in time set adjust routines

fixaddr        = $c0
dispbrit        = $88    '$88 (136DEC)  min bright.   $8F (143DEC) max bright
autoaddr        = $40
readmode         = $42

init:
high strobe            ;Ensure strobe is initially high
gosub clearchars        ;Clear all characters
blinky = 010000 ' 010000 would Enable output at 1Hz blink rate, start w/ relay click ON..  000000 is no blink.

' Set the time on the DS1307 RTC
i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
pause 50
'\/ \/ \/ \/ Un_REM THESE LINES (BELOW) IF SETTING UP A NEW RTC  \/ \/ \/ \/
#rem
' Set the RTC chip time
;  write time and date e.g. to 11:59:00 on Thurs 25/12/03
'; would be "writei2c 0,($00, $59, $11, $03, $25, $12, $03, 010000)"
' readi2c 0, (b0,b1,b2,b3,b4,b5,b6,b7) reads back the data

let hours = $19        ; 01-12 Note all BCD format
let minutes = $11         ; 00-59 Note all BCD format  
let seconds = $10    ; 00-59 Note all BCD format

; program does not use for we use seconds.  Set manually in the write statement
' for SQ Wave out on RTC.  Last val: 010000 would Enable output at 1Hz blink rate.  000000 is no blink

writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 50

#endrem
'/\ /\ /\ /\ Un_REM THESE LINES (ABOVE) IF SETTING UP A NEW RTC  /\ /\ /\ /\


;--------------------------------------------------------

'I have laid out the 8 segments in the display as:

'| Segment1LEFT | Segment2LEFT | Segment3LEFT | Segment4LEFT | Segment1RIGHT | Segment2RIGHT | Segment3RIGHT | Segment4RIGHT

    ;Segment Values                0-9   = ( 0 , 1,  2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
    '                              10-19 =   A , b , C , d , E , F , g,  H, i,  J,
    '                              20-29 =   K,  L,  M,  N,  o,  P,  q,  r, S,  T, 
    '                              30-35 =   U, V, W,  X,  y,   Z ,
    '                              36-44 =   segA, segB, segC, segD, segE, segF, segG, dp, off)

'the 'gosub display' routine expect 8 values; SegmentxLEFT and SEGMENTxRIGHT coded as
'lookup values shown in the rem above.

main:

if s1 = 1 or s2 = 1 or s8 = 1 then 'setting the clock time or relay ticker
    if s1 = 1 and s2 = 0 then 'setting hours
        junkread = junkread + 1
        if junkread > 23 then
            junkread = 0
        end if
        lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23), hours
         i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif ' s1 = 1, setting hours
   
    if s2 = 1 and s1 = 0 then 'setting minutes
        junkread = junkread + 1
        if junkread > 59 then
            junkread = 0
        end if
        lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59), minutes
         i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif 's2 = 1, setting minute
   
    if s1 = 1 and s2 = 1 then 'reset seconds to 00
        seconds = $00
        i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 10
    endif 'settin seconds to zero

    if s8 = 1 then ' turn on/off the clicking relay
        'read the RTC to dected the seconds for the write to RTC below keeps them accurate
        i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
        readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
        pause 10
        if blinky = 010000 then 'blinky from RTC is ON and clinking the relay. turn it OFF
            blinky = 000000
        else              'blinky from RTC is OFF and NOT clinking the relay. turn it ON
            blinky = 010000
        end if
        i2cslave %11010000, i2cslow, i2cbyte
        writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
        pause 500
    end if
   

else ' not settign the clock, check for brightness adjust and run as normal; so read a new 4letter word
   
    if s7 = 1 then 'increase brightness
        dispbrit = 140 'other values cause random LED7 behavior
    end if

    if s6 = 1 then 'decrease brightness
        dispbrit = 136   ' 136 is min bright
    end if   

sertxd (#dispbrit, 13,10)
   
    'Get SegmentxLEFT values for clock by reading the RTC
    i2cslave %11010000, i2cslow, i2cbyte    ; set PICAXE as master and DS1307 slave address
    readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
    pause 10
    gosub ReadEEPROM  ' read the four letter word.  These are loaded into SegmentxRIGHT vars
    gosub Ticker    'ticks thru the R/G LEDs to show seconds
endif

Segment1LEFT = hours & %11110000 / 16  'BCD so shift upper 4 bits to lower 4 bits
Segment2LEFT = hours & 001111

Segment3LEFT = minutes & %11110000 / 16   'BCD so shift upper 4 bits to lower 4 bits
Segment4LEFT = minutes & 001111

gosub display   'Put the SegmentxLEFT and SEGMENTxRIGHT characters onto the 7 seg displays.

gosub getkeys        ;Read tact buttons
           
goto main

'-------------------------------------------------------
Ticker: 'ticks thru the R/G LEDs to show seconds by cycling through each LED address

junkread = seconds & %11110000
junkread = junkread / 16 * 10
seconds = seconds & 001111
seconds = junkread + seconds

lookup seconds, (1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,5,5,5,5,5,5,5,5,7,7,7,7,7,7,7,7,9,9,9,9,9,9,9,9,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,15,15,15,15), dataio

dataio = dataio + fixaddr    ;LEDs are at odd addresses 1 to 15
junkread = dataio   'used to turn off LED later in this sub
low strobe
gosub sendchar

LEDTicker = LEDTicker + 1

if LEDTicker = 2 then
    LEDTicker = 1
end if

dataio = LEDTicker  'Light the LEDs.  1 = RED.  2 = GREEN.  3 = R/G
gosub sendchar
high strobe

'Turn off LED here
dataio = junkread   
low strobe
gosub sendchar
dataio = 0   '0 turns off the currently selected LED
gosub sendchar
high strobe;

dataio    = dispbrit        ;Display control on, brightness level
low strobe                 ;Strobe low
gosub sendchar
high strobe                ;Strobe high

return 'Ticker




ReadEEPROM:
'24LC256 EEPROM is loaded with 987 four letters words (3948 characters)
'Each character is an address from 0 to 3947
'readi2c addrs, (charvalue)

i2cslave %10100000, i2cslow, i2cword    ; set PICAXE as master and DS1307 slave address

'Read and Translate the char read from the EEPROM for the lookup(.,...), dataio command.
'Read the EEPROM letter then subtract 87 from that ASCII value for the "lookupchar" sub.  Examples:
'ASCII value for a = 97; Lookup in this program value is 10.  So, 97 - 87 = 10
'ASCII value for j = 106; Lookup in this program value is 19.  So, 106 - 87 = 19
'ASCII value for k = 122; Lookup in this program value is 35.  So, 122 - 87 = 35


readi2c w10, (Segment1RIGHT)
Segment1RIGHT = Segment1RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment2RIGHT)
Segment2RIGHT = Segment2RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment3RIGHT)
Segment3RIGHT = Segment3RIGHT - 87

w10 = w10 + 1
readi2c w10, (Segment4RIGHT)
Segment4RIGHT = Segment4RIGHT - 87

w10 = w10 + 1
'check if "yurt" (the last possible word) is displayed?
if Segment1RIGHT = 34 AND Segment2RIGHT = 30 AND Segment3RIGHT = 27 AND Segment4RIGHT = 29 then 'yes. it is "yurt"
    w10 = 0   'yurt' found, so go back to address 0 (the first 4letter word)
end if

pause 1000 'keep the secs LED on and slow down the words

return ' ReadEEPROM


;--------------------------------------------------------

display:    ;Displays data on the 7 seg displays, using 2 blocks of 4 digits

    bank = 0   ;LEFT Side: First block of digits

    dataio    = fixaddr + bank + 0 ;Set Leftmost 7 seg, LEFT Side write address
    low strobe                 ;Strobe low
    gosub sendchar
    char = Segment1LEFT        ;Leftmost 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe                ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, LEFT Side write address
    low strobe                 ;Strobe low
    gosub sendchar
    char = Segment2LEFT        ;Leftmiddle 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe                ; End of data - Strobe high
   
    dataio    = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, LEFT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment3LEFT    ;Rightmiddle 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 6 ;Set Rightmost 7 seg, LEFT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment4LEFT    ;Rightmost 7 seg, LEFT Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    'RIGHT BANK
    bank = 8  ;RIGHT Side: Second block of 4 digits
    dataio    = fixaddr + bank + 0 ;Set Leftmost 7 seg, Right Side write address
    low strobe             ;Strobe low
    gosub sendchar
    char = Segment1RIGHT    ;Leftmost 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, Right Side write address
    low strobe             ;Strobe low
    gosub sendchar
    char = Segment2RIGHT    ;Leftmiddle 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ;End of data - Strobe high
   
    dataio    = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, Right Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment3RIGHT    ;Rightmiddle 7 seg, Right Side
    gosub lookupchar
    gosub sendchar
    high strobe            ; End of data - Strobe high
   
    dataio    = fixaddr + bank + 6 ;Set Rightmost 7 seg, RIGHT Side write address
    low strobe             ; Strobe low
    gosub sendchar
    char = Segment4RIGHT    ;Rightmost 7 seg, RIGHT Side
    gosub lookupchar
    gosub sendchar   
   
    '-----------------
   
    'must refresh dispbrit each time
    dataio    = dispbrit ;Display brightness level. $88 (136DEC)  min bright.   $8F (143DEC) max bright
    low strobe         ; Strobe low
    gosub sendchar
    high strobe        ; Strobe high

return   'display

;--------------------------------------------------------

clearchars:            ;Clear LEDs and 7 seg displays.  ALL LEDS OFF. Segs and LEDs
    dataio    = autoaddr ; Data mode auto increment
    low strobe         ; Strobe low
    gosub sendchar
    high strobe        ; Strobe high
    ;
    low strobe         ; Strobe low
    dataio    = fixaddr ; Set start address
    gosub sendchar
    for tmpry = 1 to $0f    ;$0F = 15, so loop runs 16 times.  7 LEDs and 7 seg displays
        dataio = 0        ;Zero blanks the  display
        gosub sendchar
    next
    high strobe            ;Strobe high, keep low to end of data
return

;--------------------------------------------------------

sendchar:    ;Routine to send all characters to LKM1638 module serially
    pad        = $ff    ;$FF = 255.  Set counter
    high clock        ;Ensure clock is high for pulseout
    do
      pinc.1 = bit0    ;Make c.1 the value in bit0
      iobuf = iobuf/2    ;Shift right
      pulsout clock,1 '10us clock pulse
    loop Until pad = 0  'excecute 256 times
return

;--------------------------------------------------------

getkeys:    ;Reads the input tact buttons in and places them in bits16 to bits23
dataio    = readmode    ; Data mode read
low strobe
gosub sendchar
input c.1            ;set c.1 as input
high clock            ;Ensure clock is high for pulseout
for tmpry = 1 to 16    ;Read in bits 0-15
    bit0 = pinc.1    ;Make bit0 the value on c.1. Need to use c.1 as it is both in & out
    iobuf = iobuf*2    ;Shift bit left
    pulsout clock,1    ;10us clock pulse, read next bit
next
s6 = bit3            ;Move 1st word switch values out of buffer
s2 = bit7
s5 = bit11
s1 = bit15
for tmpry = 1 to 16    ;Read in bits 16-31
    bit0 = pinc.1    ;Make bit0 the value on b.0. Need to use c.1 as it is both in & out
    iobuf = iobuf*2    ;Shift bit left
    pulsout clock,1    ;10us clock pulse, read next bit
next
s8 = bit3            ;Move 2nd word switch values out of buffer
s4 = bit7
s7 = bit11
s3 = bit15   
output c.1            ;Return c.1 to output
high strobe
return

;--------------------------------------------------------

lookupchar:    ;Looks up the code to display the digit in 'char' on the 7 seg display
    ;character  0-9   =    ( 0 , 1,  2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
    '               10-19 =         A , b , C , d , E ,  F , g,  H, i,   J,
    '               20-29 =         K,  L,  M,  N, o,   P,  q,  r,  S,  T, 
    '               30-35 =         U, V, W,  X,  y,   Z ,
    '               36-44 =        segA, segB, segC, segD, segE, segF, segG, dp, off)

    lookup char,(63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113,111,118,16,30,118,56,21,84,92,115,103,80,109,120,62,28,42,118,110,91,1,2,4,8,16,32,64,128,0),dataio

return
;-----------------------------------------
-----
Link back: Hack A Day
-----