Introduction: A Wirelessly Controlled, Arduino-Powered Message Board

Build yourself a wirelessly controlled, Arduino-powered message board!

Here's an easy project which creates a wirelessly programmable message board. It uses XBee modules to provide a wireless serial link between your computer and the device. You interact with it via a simple menu system. There are no buttons (other than the reset button, which is hidden) on the device.

Step 1: What's It All About

The Arduino has three types of memory: flash, EEPROM and RAM. In this project, we use all three to store messages and values. Since we can't change the flash from inside the program, we use it to store 'canned' messages, or messages that don't change. We keep these short so they fit right on the display. We can also use the RAM to store a message, but we don't have much, so we keep that short too. The internal EEPROM is only 512 bytes, but that's enough to store a program of which canned messages to display and for how long. Finally, we add an external serial EEPROM, in which we store a long message (up to the whole size of the EEPROM) that we scroll across the display.

The following programming concepts are demonstrated:
- creating a simple menu system using the serial interface
- accepting and validating strings and integers via the serial interface
- retrieving strings from flash memory using progmem
- storing and retrieving strings in external EEPROM using a simple data structure
- storing configuration data in the onboard EEPROM
- displaying static and scrolling text on a parallel interface LCD (or LCD compatible display)
- measuring an analog value, in this case light levels

Step 2: Design Criteria

The program has to do three things. First, it has to write data to the display in one of the two methods - direct or scrolling. While doing so, it has to be ready to jump to the user menu routine as soon as serial data appears at the serial input. Finally, the program must read and write to the on-board EEPROM and the external serial EEPROM.

The hardware must be Arduino-compatible, support an XBee without too much soldering and have some protoboard space to accommodate interfacing the display, serial EEPROM and LDR. I wanted it to be easily-readable, but still work with the LiquidCrystal library, so I selected an LCD-compatible VFD character display.

For the Arduino, I chose Sparkfun's Arduino Pro 328 5V.
- It's cheap
- It mates withe the Sparkfun Xbee shield (more about this in a bit)
- It has no USB, which I won't need once it's initially programmed
* it puts 5V on the 3.3v pin, which might be a problem. However, the Sparfun Xbee shield has it's own TTL level converter and 3.3v regulator

For the Xbee, I chose standard power, Series I XBee units. They seem to have plenty of range for the application. The more complex Series II features are not needed for this project.

To mount the Xbee, I chose Sparkfun's Xbee shield.
- It's expensive, but convenient
- It has just enough protoboard space
* It does not break out all the pins. I was able to solder right to the pins, but this might be an issue for some

While I was at it, I also purchased a Sparkfun Xbee USB carrier. You need it to interface the Xbee to your computer.

The VFD chosen was a Noritake CU2009ECPB-W1J.
- It was available cheaply on eBay
- It is LCD-compatible
* It draws a lot of power - 400 ma at 5v.

The serial EEPROM is a 24LC256 256K EEPROM from Microchip.
- It's cheap
- It's an I2C part, so only needs two pins
- It is supported by Wire.h and is easy to use

The power supply is a 9v wall-wart. The Xbee and Arduino handle their own voltage regulation, while the display gets fed from a separate 7850-based linear regulator. It drops 4v at 400ma and dissipates a lot of heat in doing so. In hindsight, a better choice would have been an external 5V switchmode wall wart.

Finally, for the case... a wooden cigar box. It's easy to work with, came with a nice level of fit and finish and was cheap - $3.

Step 3: The Code

Here's the code to the project. Much of it is taken up with the serial menu routine and progmem directives to store strings in the flash memory. I wrote it initially to remind my kids of their chores. You can change the strings to suit your needs.

Initially I just had my serial menu responses as Serial.println commands, but this gets stored in precious RAM. Instead, I have re-written the code to use PSTR so that strings are still in the code where you expect but get stored in FLASH at compile time. You could use the typical PROGMEM scheme of multiple arrays, but that makes the code very hard to follow.

I used the example found here It defines an extension that allows PSTR to work in a function that does the same thing as Serial.println, only with a pointer to a prog_uchar instead of char.

Step 4: The Circuit

The diagram below shows the connections to be made in the protoboard area. Connections between the ATMEGA and XBee are not shown as they are taken care of by the XBee shield. If you don't use the shield, you'll need your own level shifter between the ATMEGA and the XBee, as well as a 3.3v regulator for the XBee. Decoupling capacitors and other passives are also assumed.

The transistor pull-down circuit for the XBee programming/reset can be seen on my protoboard, but is not included here.

If you want to build your own 7805-based regulator, it's just the regulator and a couple of capacitors. You can easily find a schematic online. I recommend using a regulated 5V switchmode wall adapter instead of a linear regulator if you're going to use a VFD like I did. If you use a regular LCD, a 7805 is fine and might not even need a heatsink. There are also better choices in linear regulators, if you're willing to pay more. Some have a forward voltage drop as low as 0.5v.

If you're going to use a PCB-mounted power jack on protoboard, make sure to buy one with standard 0.1" spaced pins. The more common style has larger spades which is stronger but won't work on a protoboard.

In the next steps, I go over some of the code which might be useful to the beginning Arduino programmer.

Step 5: Useful Code Explained - Serial Polling

Polling for serial input:
This is easy. Serial.available() is non-zero when there is data to be read. When we want to pause for something, eg. between messages, we just poll Serial.available() over and over while looping for a period of time. If anything shows up, we break the loop:

// pause between messages
previousMillis = millis();
while (millis() - previousMillis < INTER_DELAY)
   if (Serial.available() > 0)
   breakout = 1;
   // break the delay loop
if (breakout == 1)
   breakout = 0;
   // break the message program loop

Step 6: Useful Code Explained - Clearing a Serial Terminal

It does what it says - clears the screen and sends the cursor to home. Note that the Arduino serial monitor is not really a terminal. It does not support the control characters necessary for this to work. You need to use a real terminal emulator program with this project.

// function clearAndHome()
// clear the terminal screen and send the cursor home
void clearAndHome()
Serial.print(27, BYTE); // ESC
Serial.print("[2J"); // clear screen
Serial.print(27, BYTE); // ESC
Serial.print("[H"); // cursor to home

Step 7: Useful Code - Storing Multiple Data Types in the EEPROM

How do you store multiple types of data in the internal EEPROM when you can only write bytes? I needed to store a message array index and a display time. The easiest solution was to store one at odd addresses and the other at even. I won't paste the code here, but it is documented well in the source.

More complicated data structures, like strings, take more thought as you are still dealing with bytes. I show the simplest method for strings on the next page. There are also libraries available which allow you to store complex data types in the EEPROM.

Step 8: Useful Code - Reading Strings From the Serial EEPROM

With the standard Arduino libraries, you can only write and read bytes to and from the EEPROM. So how do you handle strings? Remember, in C/C++ (and other languages) a string is a number of characters terminated by "\0". So, just write your characters as bytes to the EEPROM and terminate with a "\0". You can keep this up until you're bored or almost out of EEPROM space. I reserve the last two addresses so I can always tack on a "\0" and a ctrl-d, which I use to mark the end of the text.

I decided to break up the input into 200-character strings, regardless of the user input. This is done because I was initially not sure what I was going to do with the strings, so I buffer them in a 200 character array when reading them.

To read a character from the EEPROM into the array, I read a character from the array at position X and also X+1, staring at address 0. If X+1 is not a "\0" or a ctrl-d, I store character X in the array. I use pointer arithmetic to move along the array and dereference the pointer to store and fetch data.

If you wanted to, you could search the EEPROM address space for X number of "\0"s and perhaps have random messages or let the user choose one. I just read the whole thing start to finish. The only thing I use the string functionality for is "wear-leveling" on the VFD - the strings alternate scrolling along the top and bottom rows of the display.

Step 9: Useful Code - Dimming the VFD

The Noritake LCD-compatible VFD I chose supports 4 brightness levels - 100%, 75%, 50% and 25%. The display is very bright at the brightest setting and would look glaring in a dark room. I wired up a LDR with a 10K resistor to form a voltage divider. This changing voltage is read at pin A0. I update the brightness at every new string. You could certainly do it more often if you wanted.

Here's the brightness setting function:
// function setDisplayBright()
// sets the brightness of a Noritake LCD-compatible vfd
// based on ambient light level
// requires customized LiquidCrystal library with lcd.vfdDim()
// expects nothing
// return nothing
// uses LDRreading and LDRpin as globals
void setDisplayBright()
LDRreading = analogRead(LDRpin);
if (LDRreading > 800)
else if ((LDRreading <= 800) && (LDRreading > 450))
else if ((LDRreading <= 450) && (LDRreading > 200))
// Serial.println(LDRreading);

What's lcd.vfdDIm()? It's a function I added to my local copy of the LiquidCrystal library. Here's how to add it:

Edit the following library files under the LiquidCrystal library directory. Note the '>' is from diff, which shows how it is different from the original version:


< // set dimming on Noritake LCD-compatible VFD
< // dimming levels:
< // 0 = 100%
< // 1 = 75%
< // 2 = 50%
< // 3 = 25%
< void LiquidCrystal::vfdDim(uint8_t dimming) {
< if ((dimming > -1) && (dimming < 4)) {
< digitalWrite(_rw_pin, LOW);
< send(0x28, LOW);
< digitalWrite(_rw_pin, HIGH);
< send(dimming, HIGH);
< digitalWrite(_rw_pin, LOW);
< }
< }


< void vfdDim(uint8_t dimming);

Step 10: Last Trick - Getting an Integer From Serial Input

The serial input deals only in characters. So how do you get an integer? Get characters, make sure they are digits, and shift them left in the number by multiplying by ten each time. When you get a CR, the number is done:

// function getSerialInt()
// use serial input to get an integer
int getSerialInt()
char inChar;
int in;
int input = 0;


while (Serial.available() > 0)
inChar =;
// echo the input
// convert 0-9 character to 0-9 int
in = inChar - '0';
if ((in >= 0) && (in <= 9))

// since numbers are entered left to right
// the current number can be shifted to the left
// to make room for the new digit by multiplying by ten
input = (input * 10) + in;
// stop looping when an ^M is received
while (inChar != 13);
// return the number
return input;

Step 11: Setting Up the XBee Radios

To use the Xbees as a simple serial link, all you have to do is set the PAN on each to the same number. It can be done from any terminal emulator. Follow the excellent tutorial here:

To set up the radios to allow remote programming is more work. It probably can be done in a terminal emulator, but it is much easier to do using the Windows-only software for the purpose. It also requires some simple extra hardware - a transistor to pull down the hardware reset pin - the Xbee alone can not do it.

I tried this and despite checking over my XBee config and my RTS pin wiring and transistor pull-down circuit, I could not keep the serial input from conflicting with the reset line. I've left the connection to the reset pin disconnected for now, since I don't feel a pressing need to program remotely

Here is an excellent tutorial if you wish to try it:

Step 12: Building a Case

Perhaps the hardest part of a microcontroller project is how to house it. If you can find them, wooden cigar boxes work great. You can easily cut them with a hobby hand saw and a regular hand drill makes holes in them well.

The green plexiglass was purchased long ago off eBay. It's not required but it certainly improves the contrast of the display. Sheets of this sort of thickness are most easily cut by the score and snap method.

I did not want screw heads showing on the front of my project box, so I went with flat head screws though the back to secure the boards. I was short on standoffs after the VFD was mounted, so I used an extra nut below each board along with some Loctite on the threads to create a small standoff.

Finally, hot melt glue secures the plexiglass to the wood. Ugly, but only on the inside.

Step 13: Power Supply

I just happened to have a powerful enough 9V DC wall-wart on hand to allow the use of a 7805-based linear regulator. It definitely puts out a lot of heat. In fact, it probably makes almost as much heat as the circuit uses power. Use a 5V DC regulated switchmode wall-wart instead.

Step 14: Using It

Here are some screenshots showing how the serial menus look.

On a Unix or Linux machine, 'screen' works well as a terminal. Watch your /dev tty devices as you plug in your USB XBee carrier and use the new device in your screen command. Example:
'screen /dev/tty.usbserial-A700eYMa 19200'. Btw, I set my XBees to 19200 baud.

Step 15: Conclusion

Congratulations! You stuck with it to the end!

I hope you'll be able to use some of the code presented here in your own projects.


supermaggel made it!(author)2012-05-21

This is nice!

This is probably really clear, but arduino's API changed over time,
the correct way of doing this would be:

void clearAndHome()
Serial.print("[2J"); // clear screen
Serial.write(27); // ESC
Serial.print("[H"); // cursor to home

Anyone has a tip on which terminal (for OSX) program actually understands these commands? I've tried coolTerm, which doesn't seem to understand this.
Zterm any good? and goSerial? Thanks!

yadoo86 made it!(author)2012-02-25

If you use Arduino 1.0 and you want to clear the screen -like the step 6-, I highly recommend this article:

uhclem made it!(author)2011-10-17

New version!

I have updated the code so that a Serial.println-type function can be used with "inline" FLASH-stored strings. So, basically, you have code that has a print function and strings where you would expect them in the code, only at compile time they get stored in FLASH and read-from FLASH at execution-time via PROGMEM.

Perhaps at some point Serial.print/println will be rewritten to use prog_uchar instead of char, and then PSTR will be able to provide a pointer it can use directly. But, this method works fine.

frank26080115 made it!(author)2011-02-08

Wow nearly all the RAM?

You need to use flash memory more. It looks like you know how but you are still using the "Serial.print", which is going to eat up a lot of RAM.

Use stdio.h , so you can use printf_P

together with avr/pgmspace.h, you can call functions such as

printf_P(PSTR("This string will take no RAM at all"));

uhclem made it!(author)2011-02-09

These are good comments. The great thing about the Arduino platform is you can succeed right from the start with what's on the website and then later move on to standard avr libraries once you have more experience.

frank26080115 made it!(author)2011-02-08

There's a much better way of storing different data types into EEPROM

take a look at

It provides functions for most common datatypes. And for custom datatypes like a struct, simply pass in a pointer to eeprom_write_block, and the length should just be "sizeof" the struct

About This Instructable




Bio: I don't use instructables any more, so please consider all my projects here as archival.
More by uhclem:Squareinator - A SN76489 Monosynth2014 Chevy Camaro Disable OnStarThe Tnychron Clock
Add instructable to: