VFD Clock Connects to the Internet


Here is, finally, one week later than planned, the sequel to my initial Ice Tube VFD clock .

It is able to :

  1. get the time from the internet and automatically synchronise itself (why have to set the time and use a battery to keep it running when disconnected, if we have Internet access ?)
  2. periodically fetch some weather data and display it
  3. do searches on Twitter and display the 1st result

So rather than “simply” displaying the time, every 15secs it alternates between the time, the weather and a Twitter message.

Building this clock from Ladyada’s kit was all nice and fun, but almost too simple to be fully rewarding…

So I started having a look at the firmware and trying to modify it to make it do more interesting stuff.

Step 1 – Add scrolling text

Adding custom text wasn’t hard, I just had to insert a new mode in the state machine logic, and then to make it scroll I had to call a custom method from one of the interrupts occurring every millisecond that would move all the display[] array (containing the text currently displayed) one position to the left.

VFD Clock Scrolling Hello World

The ease with which I was able to insert new logic into the code, and get something working in literally a couple of hours, shows how nicely the code was initially written… all the logic is in one big file, which might seem hard to follow initially, but it’s nicely commented and divided into methods… thank you Ladyada !

Step 2 – Connect to the serial port

There’s not much point in being able to display some custom text, if it has to be hard-coded into the firmware… so the obvious next step was to make it easy to send data to the AtMega168 which drives this clock.

I hesitated between the serial and the I2C ports. I’m a big fan of I2C which I like much more than simple serial, as you can have plenty of different slaves…

I however went for serial, mainly because there was already code in the firmware that was communicating on that port for debugging purposes. There was also already a mod using this port, so overall it would be much quicker / easier to use this instead of I2C.

So let’s solder the wires to expose this port (pins 2 & 3 of the AtMega 168) :

VFDClock Soldering Wires to the Serial Port

I’ve also added 2 wires to GND and +5V.

Now we can mount the bottom of the case back :

VFDClock Soldering Wires to the Serial Port 2

And here is the final result, with the case mostly put back and the wires getting out of it through the battery hole:

VFDClock Soldering Wires to the Serial Port 3

Step 3 – Integrate a WiFi board (WiFly / RN-134)

This is all nice and good, BUT a clock like this is not supposed to stay connected to a PC to keep receiving messages… it would be nice if it could just sit there by itself, and periodically get data from the Internet and display it…

The initial idea was to simply make it display some tweets, but then I thought it would be really simple and quite useful for when I leave for work in the morning to see some weather forecast. Also, as I was working on these 2 goals I realised that with an Internet connection it is really easy to get the current exact time, so why bother setting it up or making sure the crystal doesn’t draft… just synch it up automatically !

So after some research, it appeared that the easiest way to wirelessly connect a small micro-controller to the Internet would be by using a WiFly board. I didn’t go for the Sparkfun shield, as the form factor is too big, but for a board using the same RN-131 module.
Here it is, the RN-134, made directly by rovingnetworks and bought from mouser:

WiFly RN-134 just received from Mouser

Here are some preliminary tests of this board:

WiFly connected to PC to check Internet connectivity

WiFly (RN-134) connected to the VFD clock - voltage converter

Do note the voltage converter between the two, as the clock microcontroller (AtMega 168) is 5V whereas the WiFly runs at 3.3V !

The Ugly

Everything looked good at this point, but unfortunately this was the beginning of a very frustrating week…

Now let’s have a quick look at what took the longest in this project: setting up the WiFly to make the HTTP requests and download the data…

This board is a great piece of equipment, don’t get me wrong, I love it… however there were some issues and it got very frustrating for a while.

I’ll also mention straight away that the Roving Networks support is great, I’ve never seen such a quick and knowledgeable reply !

In a nutshell, and without going into the numerous possible config details that this board offers I started by trying the obvious thing: use it in “HTTP mode” (set ip proto 18), which is supposed to allow one to simply specify the name of a server and the desired page and then the board will add the required HTTP headers.

Something doesn’t work quite right with this mode (probably adding some extra spaces / new lines) but believe it or not, I have spent an entire week-end trying to debug it…

The convoluted thing was that the same query was working when calling it from the PC (TeraTerm connected to the WiFly board) but would not when it was the AtMega microcontroller sending the commands !

Here are 2 threads with more details:

http://groups.google.com/group/nycresistormicrocontrollers/browse_thread/thread/686b328bc10a8b36

http://groups.google.com/group/london-hack-space/browse_thread/thread/4c40f6abc6dbad74

And by the way, a big THANK YOU to members of both the NYC and London hack spaces for their very valuable help !

Also, from what the roving networks support was saying, the HTTP mode should simply do this:
<remote string><your data><SPACE>HTTP/1.0\nHost:<SPACE><the hostname in config>\n\n

which is what the HTTP standard specifies…

In any case, the solution was to simply go into “TCP mode” (set ip proto 2 , the factory default) and manually construct the HTTP request, as you’ll see in the attached code.

And after a little more frustration, due to the fact that  I had reverted the board to factory defaults and had forgotten to remove the “remote string”, which meant that it kept adding “*HELLO*” to whatever I was sending … lol 🙂 ,  I got it working!

Again, things look frustratingly simple now, but the very confusing thing with this 2nd problem was that for some reason the weather.yahooapis.com server would happily work in spite of the extra “*HELLO*” in the request, whereas search.twitter.com would not…

The Beautiful

Code is always beautiful 🙂

Here is what I had to add to the basic firmware.

–         wiFly.h & .c – deal with controlling the WiFi board, and constructing the HTTP requests

–         textParser.h & .c– deal with parsing the text or XML outputs from the servers and search for data between 2 tags

–         serialMessages.h & .c – deal with putting all this together and keep track of 3 different connections and parsers, one for each service

–         the few methods that I had to add to the main iv.c code which do the scrolling and periodically call the updates from the servers

–         and finally the util.h & .c files, which are not mine, but I put here because I have done several modifications (don’t dare calling them “improvements” ! 🙂 )

If you’re really interested and want to replicate this mod without having to put together and compile the code yourself, then let me know and I could definitely send you a HEX file.

_________________________ wiFly.h

/*
 * wiFly.h
 *
 * Created: 17/09/2011 Author: trandi
 */

#ifndef WIFLY_H_
#define WIFLY_H_

#include "util.h"

char wf_httpOpen(const char* server, const char* page);

#endif /* WIFLY_H_ */

_________________________ wiFly.c

/*
 * wiFly.c
 *
 * Created: 17/09/2011 Author: trandi
 */

#include "wiFly.h"
#include "util.h"
#include <util/delay.h>
#include <string.h>

uint8_t findInResponse(const char * str){
	if(uart_waitChar(1000)){
		// wait for the first char
		char ch = uart_getchar();
		while(ch != 0 && ch != str[0]){
			uart_waitChar(300);
			ch = uart_getchar();
		}

		if(ch != 0){
			for(uint8_t offset = 1; offset < strlen(str); offset ++){
				uart_waitChar(300);
				if(uart_getchar() != str[offset]) return 0; // wrong char found
			}

			return 1; // good, found what we were looking for
		}
	}

	// nope, not good....
	return 0;
}

#define CMD(x) uart_emptyReadBuffer();uart_puts_nl(x);

/*
 * Immediately afterwards the response will be available on the serial link !
 * It waits a while for data to come back...
 */
char wf_httpOpen(const char* server, const char* page){
	CMD("exit")
	CMD("close")
	_delay_ms(250);
	uart_puts("$$$"); // enter command mode
	_delay_ms(250);

	CMD("ver")
	if(! findInResponse("WiFly Ver")) return 'v';

	uart_puts("open "); uart_puts(server); uart_puts_nl(" 80");
	if(! findInResponse("*OPEN*")) return 'o';
	uart_puts("GET "); uart_puts(page); uart_puts(" HTTP/1.0\r\nHost: "); uart_puts(server); uart_puts_nl("\r\n");

	return 'n';
}

_________________________ textParser.h

/*
 * textParser.h
 *
 * Created: 15/09/2011 Author: trandi
 */

#ifndef TEXTPARSER_H_
#define TEXTPARSER_H_

#include "util.h"

#define TEXT_DELIM '"'
#define TEXT_SEP '-'

typedef struct {
	const char* server;
	const char* page;
	const char* tagBegin;
	const char* tagEnd;
	uint8_t	useTextDelimiters;
	uint8_t* usedTextIndices;
	uint8_t usedTextIndicesSize;

	uint8_t tagBeginSize;
	uint8_t tagEndSize;
	uint8_t tagBeginPos;
	uint8_t tagEndPos;
	uint8_t currentTextIsValid;
	uint8_t currentTextIndex;

	char msg[MAX_MSG_SIZE];
	uint8_t msgSize;
	char err;
	uint8_t diffCharsCount;
} text_parser_struc;

void tp_init(text_parser_struc* tp, const char* server, const char* page, const char* tagBegin, const char* tagEnd, uint8_t useTextDelimiters, uint8_t* usedTextIndices, uint8_t usedTextIndicesSize);
void tp_start(text_parser_struc* tp);
uint8_t tp_feedChar(text_parser_struc* tp, char newChar);
uint8_t tp_isMsgReady(text_parser_struc* tp);

#endif /* TEXTPARSER_H_ */

_________________________ textParser.c

/*
 * textParser.c
 *
 * Created: 15/09/2011  Author: trandi
 */

#include <avr/pgmspace.h>
#include <string.h>
#include <ctype.h>
#include "textParser.h"

void tp_init(text_parser_struc* tp, const char* server, const char* page, const char* tagBegin, const char* tagEnd, uint8_t useTextDelimiters, uint8_t* usedTextIndices, uint8_t usedTextIndicesSize){
	tp->server = server;
	tp->page = page;
	tp->tagBegin = tagBegin;
	tp->tagEnd = tagEnd;
	tp->useTextDelimiters = useTextDelimiters;
	tp->tagBeginSize = strlen(tagBegin);
	tp->tagEndSize = strlen(tagEnd);

	tp->usedTextIndices = usedTextIndices;
	tp->usedTextIndicesSize = usedTextIndicesSize;

	// default values
	tp->err = 'a';

/*
	uart_puts("Text Parser initialised with: ");
	uart_puts(tagBegin);
	uart_puts(tagEnd);
	uart_puts("\n\r");
*/
}

void tp_start(text_parser_struc* tp){
	tp->tagBeginPos = 0;
	tp->tagEndPos = 0;
	tp->msgSize = 0;
	// if we don't use text delimiters then ALL text inside is valid
	tp->currentTextIsValid = tp->useTextDelimiters ? 0 : 1;
	tp->currentTextIndex = 0;
	tp->diffCharsCount = 0; // assume the message hasn't changed...

	storeChar(tp, TEXT_SEP);storeChar(tp, TEXT_SEP);storeChar(tp, TEXT_SEP);
}

uint8_t tp_isMsgReady(text_parser_struc* tp){
	return ((tp->tagEndPos >= tp->tagEndSize) || (tp->msgSize >= MAX_MSG_SIZE));
}

void storeChar(text_parser_struc* tp, char ch){
	if(ch != tp->msg[tp->msgSize]){
		tp->msg[tp->msgSize] = ch;
		tp->diffCharsCount ++;
	}
	tp->msgSize++;
}

uint8_t tp_feedChar(text_parser_struc* tp, char newChar){
	// can't store anymore chars, no point in continuing !
	if(tp_isMsgReady(tp)) return 1;

	if(tp->tagBeginPos < tp->tagBeginSize){
		// still looking for the beginning tag
		if(newChar == tp->tagBegin[tp->tagBeginPos]){
			tp->tagBeginPos ++;
		}else{
			uint8_t prevPos = tp->tagBeginPos;

			// false hope, wrong tag, start from the beginning
			tp->tagBeginPos = 0;
			// start from the beginning with the latest chars received, which we know are the same as the beginning of _tagBegin. In case there are repetitions in the tag
			for(uint8_t i=1; i<prevPos; i++) tp_feedChar(tp, tp->tagBegin[i]);
		}
	}else if(tp->tagEndPos < tp->tagEndSize){
		if(tp->useTextDelimiters){
			if(tp->currentTextIsValid){
				// in the middle of valid text
				if(TEXT_DELIM == newChar){
					// end of valid text
					tp->currentTextIsValid = 0;
					tp->currentTextIndex ++;
				}else if (tp->usedTextIndices[tp->currentTextIndex]){
					// normal data, store it
					storeChar(tp, newChar);
				}
			}else{
				if(TEXT_DELIM == newChar){
					// beginning of valid text
					tp->currentTextIsValid = 1;
					// add a delimitator with the previous text
					if (tp->usedTextIndices[tp->currentTextIndex]) {
						storeChar(tp, TEXT_SEP);
					}
				}else {
					if(newChar == tp->tagEnd[tp->tagEndPos]){
						// this might be the end
						tp->tagEndPos ++;
						if(tp->tagEndPos >= tp->tagEndSize){
							storeChar(tp, 0); //end of string
						}
					}else{
						uint8_t prevPos = tp->tagEndPos;

						// false hope, wrong tag, start from the beginning
						tp->tagEndPos = 0;
						// start from the beginning with the latest chars received, which we know are the same as the beginning of _tagBegin. In case there are repetitions in the tag
						for(uint8_t i=1; i<prevPos; i++) tp_feedChar(tp, tp->tagEnd[i]);
					}
				}
			}
		}else { //! tp->useTextDelimiters
			if(newChar == tp->tagEnd[tp->tagEndPos]){
				// this might be the end
				tp->tagEndPos ++;
				if(tp->tagEndPos >= tp->tagEndSize){
					storeChar(tp, 0); //end of string
				}
			}else if (tp->tagEndPos > 0) {
				// we thought we started the end tag and actually not...
				uint8_t prevPos = tp->tagEndPos;

				// false hope, wrong tag, start from the beginning
				tp->tagEndPos = 0;
				// start from the beginning with the latest chars received, which we know are the same as the beginning of _tagBegin. In case there are repetitions in the tag
				for(uint8_t i=1; i<prevPos; i++) tp_feedChar(tp, tp->tagEnd[i]);
			}else {
				// normal data, in the middle of the text store it
				storeChar(tp,  newChar);
			}
		}
	}

	// hasn't yet finish, there's only one way of finishing...
	return tp_isMsgReady(tp);
}

_________________________ serialMessages.h

/*
 * serialMessages.h
 *
 * Created: 17/09/2011  Author: trandi
 */

#ifndef SERIALMESSAGES_H_
#define SERIALMESSAGES_H_

typedef struct {
	uint8_t time_s;
	uint8_t time_m;
	uint8_t time_h;
} time_date_struc;

void sm_init(void (*funcSignalUpdt)(void));
time_date_struc* sm_getTime();
char* sm_getMsg(uint8_t switchMode);
void sm_updateTwitter();
void sm_updateWeather();

#endif /* SERIALMESSAGES_H_ */

_________________________ serialMessages.c

/*
 * serialMessages.c
 *
 * Created: 16/09/2011 Author: trandi
 */

#include <util/delay.h>
#include "serialMessages.h"
#include "util.h"
#include "textParser.h"
#include "wiFly.h"
#include <stdlib.h>

static char ERR[] = {'e', 'r', 'r', ' ', ' ', ' ', ' ', ' ', 0};
#define setErr(x, y) ERR[4] = x; ERR[5] = y

static uint8_t UTI_YAHOO_WEATHER[] = {1, 0, 1, 1, 1, 0};
static uint8_t UTI_TWITTER[] = {1};

text_parser_struc _yahooWeatherParser;
text_parser_struc _twitterParser;
text_parser_struc _timeParser;
time_date_struc _timeDate;

// pointer to the function to call when we receive a new message
void (*funcSignalUpdate)(void);

uint8_t _mode = 0; // 0 - weather, 1 - twitter

void sm_init(void (*funcSignalUpdt)(void)){
	funcSignalUpdate = funcSignalUpdt;

	tp_init(&_yahooWeatherParser,
			"weather.yahooapis.com",
			"/forecastrss?w=44418&u=c",  // London, in Celsius
			"<yweather:forecast",	// tag begin
			"/>",					// tag end
			1,						// do use the '"' values delimiters, next params are used
			UTI_YAHOO_WEATHER,		// which bits of text to use
			6						// how many bits have we defined
	);

	tp_init(&_twitterParser,
			"search.twitter.com",
			"/search.json?q=arduino&rpp=1",
			"\"text\"",				// tag begin
			"\"to_user_id\"",		// tag end
			1,						// 0 if we wanted NO '"' text delimiters, next 2 params are ignored !
			UTI_TWITTER,			// which bits of text to use
			1	// how many bits have we defined
	);

	tp_init(&_timeParser,
			"www.timeanddate.com",
			"/worldclock/city.html?n=136",
			"=big>",				// tag begin
			"</strong>",			// tag end
			0,						// 0 if we wanted NO '"' text delimiters, next 2 params are ignored !
			0,			// which bits of text to use
			0	// how many bits have we defined
	);
}

void getSerialMsg(text_parser_struc* tp){
	tp->err = '1';
	if(uart_waitChar(3000)){ // be generous with the initial wait !
		tp_start(tp);

		while(uart_waitChar(1000) && ! tp_feedChar(tp, uart_getchar()));
		tp->err = '2';

		if(tp_isMsgReady(tp)) tp->err = 'n'; // everything ok

		// the message is done, but empty the buffer
		uart_emptyReadBuffer();
	}
}

char* sm_getMsg(uint8_t switchMode){
	if(switchMode){
		_mode = ! _mode;
	}

	if(_mode){
		setErr('y', _yahooWeatherParser.err);
		return _yahooWeatherParser.err == 'n' ? _yahooWeatherParser.msg : ERR;
	}else{
		setErr('w', _twitterParser.err);
		return _twitterParser.err == 'n' ? _twitterParser.msg : ERR;
	}
}

void sm_updateTwitter(){
	_twitterParser.err = wf_httpOpen(_twitterParser.server, _twitterParser.page);
	if(_twitterParser.err == 'n') {
		getSerialMsg(&_twitterParser);

		// call the signal only if there are at least 10 different chars. For some reason there can be small diffs in messages...
		if(_twitterParser.diffCharsCount > 10){
			_twitterParser.diffCharsCount = 0;
			funcSignalUpdate();
		}
	}
}

void sm_updateWeather(){
	_yahooWeatherParser.err = wf_httpOpen(_yahooWeatherParser.server, _yahooWeatherParser.page);
	if(_yahooWeatherParser.err == 'n') {
		getSerialMsg(&_yahooWeatherParser);
	}
}

time_date_struc* sm_getTime(){
	_timeParser.err = wf_httpOpen(_timeParser.server, _timeParser.page);
	if(_timeParser.err == 'n') {
		getSerialMsg(&_timeParser);

		// the msg should be something like Saturday, "24 September 2011, 09:31:40 " extract hours and minutes
		char num[2];
		num[0] = _timeParser.msg[_timeParser.msgSize - 9];
		num[1] = _timeParser.msg[_timeParser.msgSize - 8];
		_timeDate.time_h = atoi(num);
		num[0] = _timeParser.msg[_timeParser.msgSize - 6];
		num[1] = _timeParser.msg[_timeParser.msgSize - 5];
		_timeDate.time_m = atoi(num);
		num[0] = _timeParser.msg[_timeParser.msgSize - 3];
		num[1] = _timeParser.msg[_timeParser.msgSize - 2];
		_timeDate.time_s = atoi(num);
	}else{
		setErr('t', _timeParser.err);
		_timeDate.time_h = 0; _timeDate.time_m = 0; _timeDate.time_s = 0;
	}

	return &_timeDate;
}

_________________________ util.h

#ifndef UTIL_H_
#define UTIL_H_

//BRR = (F_CPU / 16 / BaudRate ) - 1
#if (F_CPU == 16000000)
#define BRRL_2400 416    // for 16MHZ
#define BRRL_9600 103    // for 16MHZ
#define BRRL_19200 52    // for 16MHZ
#define BRRL_115200 8
#elif (F_CPU == 8000000)
#define BRRL_2400 207
#define BRRL_9600 52
#define BRRL_19200 26
#define BRRL_115200 3
#endif

#include <inttypes.h>

// very tricky, uses a lot of space !
// for some reason IF this gets too big and the "Data" part of the program goes beyond 90%,
// the text parser for yahoo weather doesn't work anymore... don't ask WHY !? 🙂
#define MAX_MSG_SIZE 60

//void delay_ms(unsigned char ms);
void delay_10us(uint8_t us);
void delay_s(uint8_t s);

int uart_putchar(unsigned char c);
void uart_init(uint16_t BRR);

unsigned char uart_getchar(void);
uint8_t uart_isCharAvailable(void);
void uart_emptyReadBuffer(void);
uint8_t uart_waitChar(uint16_t timeoutMs);

void uart_putc_hex(uint8_t b);
void uart_putw_hex(uint16_t w);
void uart_putdw_hex(uint32_t dw);

void uart_putw_dec(uint16_t w);
void uart_putdw_dec(uint32_t dw);
void uart_puts(const char* str);

void RAM_putstring(char *str);
void ROM_putstring(const char *str, uint8_t nl);

#define uart_puts_nl(x) uart_puts(x);uart_puts("\r\n")

#define putstring(x) ROM_putstring(PSTR(x), 0)
#define putstring_nl(x) ROM_putstring(PSTR(x), 1)

#define NOP asm("nop");
#define nop asm volatile ("nop\n\t")

#endif /* UTIL_H_ */

_________________________ util.c

/***************************************************************************
 Ice Tube Clock firmware August 13, 2009
 (c) 2009 Limor Fried / Adafruit Industries
  Modified by Tr@ndi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "util.h"

void delay_10us(uint8_t ns)
{
  uint8_t i;

  while (ns != 0) {
    ns--;
    for (i=0; i< 30; i++) {
      nop;
    }
  }
}

void delay_s(uint8_t s) {
  while (s--) {
    _delay_ms(1000);
  }
}

// setup the main UART
void uart_init(uint16_t BRR) {
  UBRR0 = BRR;               // set baudrate counter

  UCSR0B = _BV(RXEN0) | _BV(TXEN0);  // Enable receiver and transmitter
  UCSR0C = 3<<UCSZ00;  // Frame format: 8data, No parity, 1stop bit
  DDRD |= _BV(PD1);
  DDRD &= ~_BV(PD0);

}

int uart_putchar(unsigned char c)
{
  loop_until_bit_is_set(UCSR0A, UDRE0);
  UDR0 = c;
  return 0;
}

unsigned char uart_getchar(void) {
	//if (uart_isCharAvailable())
	return UDR0;
	//return 0;
}

uint8_t uart_isCharAvailable(void) {
	return (UCSR0A & _BV(RXC0));
}

void uart_emptyReadBuffer(void){
	char ch;
	while(uart_waitChar(100)) ch = uart_getchar();
}

uint8_t uart_waitChar(uint16_t timeoutMs){
	for(uint16_t i=0; i<timeoutMs && !uart_isCharAvailable(); i++) _delay_ms(1);
	return uart_isCharAvailable();
}

void ROM_putstring(const char *str, uint8_t nl) {
    for (uint8_t i=0; pgm_read_byte(&str[i]); i++) {
        uart_putchar(pgm_read_byte(&str[i]));
	}
	if (nl) {
		uart_putchar('\n'); uart_putchar('\r');
	}
}

void uart_puts(const char* str)
{
    while(*str){
        uart_putchar(*str++);
	}
}

void uart_putc_hex(uint8_t b)
{
    /* upper nibble */
    if((b >> 4) < 0x0a)
        uart_putchar((b >> 4) + '0');
    else
        uart_putchar((b >> 4) - 0x0a + 'a');

    /* lower nibble */
    if((b & 0x0f) < 0x0a)
        uart_putchar((b & 0x0f) + '0');
    else
        uart_putchar((b & 0x0f) - 0x0a + 'a');
}

void uart_putw_hex(uint16_t w)
{
    uart_putc_hex((uint8_t) (w >> 8));
    uart_putc_hex((uint8_t) (w & 0xff));
}

void uart_putdw_hex(uint32_t dw)
{
    uart_putw_hex((uint16_t) (dw >> 16));
    uart_putw_hex((uint16_t) (dw & 0xffff));
}

void uart_putw_dec(uint16_t w)
{
    uint16_t num = 10000;
    uint8_t started = 0;

    while(num > 0)
    {
        uint8_t b = w / num;
        if(b > 0 || started || num == 1)
        {
            uart_putchar('0' + b);
            started = 1;
        }
        w -= b * num;

        num /= 10;
    }
}

void uart_putdw_dec(uint32_t dw)
{
    uint32_t num = 1000000000;
    uint8_t started = 0;

    while(num > 0)
    {
        uint8_t b = dw / num;
        if(b > 0 || started || num == 1)
        {
            uart_putchar('0' + b);
            started = 1;
        }
        dw -= b * num;

        num /= 10;
    }
}

_________________________ ADDED this bit AT THE END of iv.c

#define SCROLL_SPEED 200				// milliseconds
#define TIME_MSG_MODES_SWITCH_SPEED 15000   // milliseconds
#define TIME_MSG_TIME_UPDATE 3600	// seconds
#define TIME_MSG_WEATHER_UPDATE 3600	// seconds
#define TIME_MSG_TWITTER_UPDATE 60		// seconds

// only beeps if the alarm is on, so that we can control the stuff...
void myBeep() {
	if(alarm_on){
		beep(1500, 1);beep(3500, 1);beep(5000, 1);
	}
}

// Scrolling of the message.
void scrollMsg(){
	// Move the text
	uint8_t firstElem = display[1];
	for(uint8_t i = 1; i < _msgSize; i++) display[i] = display[i+1];
	display[_msgSize] = firstElem;
}

// alternate between SHOW_TIME and SHOW_MSG
void alternateTimeMsgModes(){
	//tick();
	if(displaymode == SHOW_TIME){
		displaymode = SHOW_MSG;
		display_str(sm_getMsg(1));
	}else if(displaymode == SHOW_MSG){
		displaymode = SHOW_TIME;
		_msgSize = 0; //stop scrolling !
	}
}

void updateTime(){
	time_date_struc* timeDate_s = sm_getTime();
	time_h = timeDate_s->time_h;
	time_m = timeDate_s->time_m;
	time_s = timeDate_s->time_s;
}

// called from an interrupt, every millisecond, for short and vital tasks
void customInterruptPeriodicActions(){
	if(_msgSize >= DISPLAYSIZE && _myMillisCounter %  SCROLL_SPEED == 0) scrollMsg();
	if(_myMillisCounter % TIME_MSG_MODES_SWITCH_SPEED == 0) alternateTimeMsgModes();
}

uint16_t _lastPeriodicActionSecs = 0;
// called from the main loop, for LONGER running tasks
void customPeriodicActions(){
	// don't even do the next calculations if the seconds have not changed
	if(_lastPeriodicActionSecs != _mySecondsCounter){
		if(_mySecondsCounter % TIME_MSG_TIME_UPDATE == 0) updateTime();

		if(_mySecondsCounter % TIME_MSG_TWITTER_UPDATE == 0) sm_updateTwitter();
		if(_mySecondsCounter % TIME_MSG_WEATHER_UPDATE == 0) sm_updateWeather();

		_lastPeriodicActionSecs = _mySecondsCounter;
	}

In iv.c, the 2 methods that “inject” my mod into the rest of the code are

  • customPeriodicActions(), called at the beginning of the infinite look within the main() method
  • and customInterruptPeriodicActions(), called in the middle of SIGNAL (SIG_OVERFLOW0) {} which occurs roughly every millisecond

WiFi VFDClock Finished

And here is the final photo, installed in its rightful place, along my collection of miniature single malts 🙂

WiFi VFDClock Final Position

As usual, if you’ve made it up to here and had the patience to read all my ramblings, then don’t hesitate to leave a commend and give some feedback…

Update 27Feb2012 _ Here are some  improvements I need/want to make:

1) RE-CONNECT when wireless is turned back on !

– SLEEP action

– set wlan join 1 – will try to join lola when comes back from sleep  (“This policy is used when the module powers up, including wake up from the sleep timer “)
– use REBOOT ? if sleep doesn’t work…
2) paint the LEDs (especially the blue one)
3) do NOT update the msg if error, keep the old ones  – Already DONE
4)  if can’t get the messages (WiFly down) then update the timers and keep trying every 20 seconds or  so for both the time and the weather !

22 Responses to VFD Clock Connects to the Internet

  1. qiwei says:

    Hi dan,
    I am not sure if you can notice my reply.
    I have to finish my project with RN-134 and RN-131 these months. The only experience related to this kind of work is that i wrote some codes on zigbee sensor nodes. I have no idea about RN-131 and I do not know where to find the answers.
    1.
    When I connect the device with my PC and reboots this device, there are something meangless on TeraTerm screen. so i think this device do connect to my PC. But when I try to enter the command mode using “$$$” quickly and without as the user guide said, nothing happened. On the board, the blue Led is on, red LED blinks quickly while the green on blink slowly.
    2.
    how can i control or send commands to RN-131 with my own implementation. In my last coursework about zigbee, i wrote my code, compile it and then upload it to the device. what is the process in RN-131 device? I want PC send command to RN-131 periodically.

    If you are not available to answer me, can you introduce me some books or materials that i can read?

    Wish you could receive my message.

    • trandi says:

      Hi,

      Thank you for your message.
      It’s been a while since I did this project so I don’t remember all the details, but from what I vaguely recollect:

      1. have you checked the transfer speed (bps rate) ? It sounds like a problem with that…

      2. I’m not sure I understand what you want to do? Do you want to run your own code on the RN131? In my project I don’t run any code on the device itself, it just works as an internet access point for the AVR micro controller that is the brain of the clock. It’s this MCU, not the RN131 itself that makes http requests and process the data. The RN131 works as an internet router if you want…

      Hope this helps,
      dan

  2. Pingback: Innovative Clock that Connects to the Internet | HACKOLOG - Amazing Hacks and Mods

  3. Seuf says:

    like the last picture !!
    I’ve just bought an Arduino Wireless Shield with a Wifly RN-XV module. My objective is to pilot a dagu rover 5 with an android app !
    I’m new to arduino programming, so i think this will not be an easy task for me 😛
    If you have good tutorials links, it could be nice 🙂

  4. Pingback: Simple serial transceiver – Aurel RTX-MID « Robotics / Electronics / Physical Computing

  5. Bohlsche says:

    hey trandi,
    first of all pls pardon me for any misspelling or linguistic inaccuracy(ain’t native-speaking english)
    I’m stuck between a Basic Atom Pro 40 Microcontroller (http://www.basicmicro.com/Basic-ATOM-Pro-40-M_p_63.html) and the Android IOIO. The basic idea is to read information from the Basic Atom Pro on an I2C-Bus and then display on the Android device (Acer Iconia Tab A500).
    To be honest, I’m a total newbie concerning the IOIO. Nevertheless, I have to get this going, no matter what the cost.
    I tried to go through your coding, even though it was not that revealing to me.
    If you could get me a first draft or any written assistance, I’d be extremely thankful.
    The point is, I basically know the coding syntax but don’t really understand how all this works, that’s why I stumble on every obstacle.
    thanks,
    Bohlsche

    • trandi says:

      Hi Bohlsche,

      I might have misunderstood you, but I don’t think you can compare the Basic Atom Pro with the IOIO.
      Simply because the Atom can NOT be connected DIRECTLY to and Android device !

      If you’re point is not comparing them but simply make them talk over I2C, then it should be fairly straight forward.

      Have you had a look at these 2 posts of mine ?

      Android IOIO Wii Motion Plus – Gyroscopes

      Android IOIO Wii Nunchuck

      They are both about getting information from a Wii device, over I2C to the IOIO and then to the Android phone.

      You have the exact code example and all the explanations.
      If you have any concrete questions about the IOIO/Android code then let me know (preferably on one of these 2 posts)

      However I can’t really help you with the code on the Basic Atom side.

      Dan

      P.S. out of curiosity WHY did you choose to use a Basic Atom ?
      The price doesn’t seem specially attractive, and I vaguely remember from 5 years ago when I used a Basic Atom Pro 24, that it was frustratingly hard to program due to the lack of community and examples around… maybe things have changed, hence my curiosity ….

      • Bohlsche says:

        hi trandi,

        thanks for your quick reply 🙂

        to put this straight: a friend of mine used the Basic Atom Pro to control a hydraulic anti-roll system on a model tractor.

        By now we are trying to install that same system he conceived on a real tractor.
        (my homeland’s alpine so this would be quite in demand)

        We still use the Atom to control all the mechanics, so we need the IOIO only to read information from the Atom (e.g. inclination angle, oil pressure, …) and display them on the Acer Tab he insists on mounting on the tractor (to “revolutionize” agriculture … ^^).

        By way of trial we managed to apply the I2COUT code on the Atom, but to program the IOIO turned out to be much more complicated, since we both don’t have experience with that.

        Nevertheless, we have to get this going, no matter the cost. So if you could get us a short draft on how to read on the Atom’s I2COUT or at least get across how the coding works, pls let us know.

        If you need any code samples or syntax description from the Basic Atom, pls contact us. We’ve spend to much time on all this to drop it now.

        For any further aid, we won’t hesitate on an appropriate recompense.

        thanks,
        Bohlsche

      • Bohlsche says:

        Buy the time choosing the Atom Pro 40 I wasn’t involved yet, so actually I don’t know the “why”. All I know is that now it would be much to laborious to reorganize.

      • trandi says:

        I’ve just sent you an e-mail directly to your e-mail address.

        dan

      • Bohlsche says:

        hi Dan,
        pls check your e-mail box.
        I’ve replied directly on your last message.
        Bohlsche

  6. Pingback: Electronics-Lab.com Blog » Blog Archive » Wifi Ice Tube Clock

  7. arckarts says:

    Very interesting, gave me some good ideas for my next project!

  8. Pingback: Wifi Ice Tube Clock « adafruit industries blog

  9. Pingback: Punerea Twitter într-un ceas VFD | ro-Stire

  10. Michael says:

    Nice work, just ordered the wifly board to get this going with my clock!

  11. Pingback: Putting Twitter in a VFD clock | You've been blogged!

  12. Pingback: Putting Twitter in a VFD clock | Earn Money Online

  13. Pingback: Putting Twitter in a VFD clock - Hack a Day

  14. Ytai says:

    Oh, here you can probably say “mostly sunny” without the Internet 🙂
    California is great so far and is expected to be even greater once I found out about all the cool stuff. Keep those great projects coming, and don’t let your IOIO gather too much dust – very exciting new features are just around the corner!

  15. Ytai says:

    You probably don’t need the Internet in the UK to say “mostly cloudy” 🙂
    Nice build!

    • trandi says:

      🙂 just laughed by myself for an entire 5 mins… you will be surprised, but this week-end we had really nice weather, with a few clouds it’s true but really nice, and, hold your breath, this week the forecast announces 27degrees ! This is hotter than summer !! 🙂

      Dan

      P.S. how’s California ?

Leave a comment