Press a button, display a random quote...

First some background.
For Valentines day, my girlfriend got me a small silver fortune cookie that she randomly puts lovey-dovey fortunes in. The idea is cute, except that I get crap for never remembering to check it for new fortunes. Ah well.

Now for my project idea, I thought it would be cool to take the concept into the Arduino. I would like to take a batch of "fortunes" and program the Arduino to display a random one with each press of a button.
Basically, I want it to start with a cute welcome message and prompt for a button press, then display a new random "fortune" with each press. I have a 4x20 LCD to use for this. I imagine that it wouldn't be very complex to achieve this, but I am not sure about the best approach for efficient use of programming and memory (storing the text strings).
I am just looking for some help getting set in the proper direction, has anyone done anything similar or know of any code examples I can try to learn from.

Thanks
James

Not necessarily the most straightforward approach, but if the messages were stored on an SD card, there could be more of them, and they could be changed without reprogramming the microcontroller.

Maybe that's v2. For v1, definitely store the messages in program memory. The program itself won't need much.

I always liked "Help, I'm being held hostage in this fortune cookie factory!"

No need for an SD card IF you put the messages in the program memory. The atmega328 has over 30k of that free and this example uses only about 3k WITH the program. This is a simple trick. The small complication is that you must copy to a regular string variable before you print. In YOUR case, using an LCD, you must remember that this memory must be accessed a little differently. Look at the say_it() routine.

Here's my take on the problem - press the button and get a "fortune" from the magic 8 ball. Yes, afaik this is the real list.

It displays to the serial port instead of an LCD because that was easier for me to test without leaving my chair. My LCD shield is in a different room.

Only 1 line needs to change to use with Arduino pre 1.0 - the "Hello, world" string constant is used directly from program memory with Serial.println(F("Hello, World!")); That doesn't work with the older software but the fix is simple. Instead use Serial.println("Hello, World!"); This will use up 14 more bytes of your RAM upon execution to store the string and is another demo of using program memory for strings. Without the F(), it works for 1.0 too.

// Written by JEDtoo on the arduino.cc forum on 1 March 2012
// as a demo for program memory.
// Use at your own risk - no performance at any task is implied.
// If your girlfriend doesn't like her "fortune" that is your
// business, not mine.
//
#include <avr/pgmspace.h>

prog_char s1[] PROGMEM = "It is certain";
prog_char s2[] PROGMEM = "It is decidedly so";
prog_char s3[] PROGMEM = "Without a doubt";
prog_char s4[] PROGMEM = "Yes - definitely";
prog_char s5[] PROGMEM = "You may rely on it";
prog_char s6[] PROGMEM = "As I see it, yes";
prog_char s7[] PROGMEM = "Most likely";
prog_char s8[] PROGMEM = "Outlook good";
prog_char s9[] PROGMEM = "Signs point to yes";
prog_char s10[] PROGMEM = "Yes";
prog_char s11[] PROGMEM = "Reply hazy, try again";
prog_char s12[] PROGMEM = "Ask again later";
prog_char s13[] PROGMEM = "Better not tell you now";
prog_char s14[] PROGMEM = "Cannot predict now";
prog_char s15[] PROGMEM = "Concentrate and ask again";
prog_char s16[] PROGMEM = "Don't count on it";
prog_char s17[] PROGMEM = "My reply is no";
prog_char s18[] PROGMEM = "My sources say no";
prog_char s19[] PROGMEM = "Outlook not so good";
prog_char s20[] PROGMEM = "Very doubtful";

// this table is in ordinary memory but has pointers
// that reference the flash PROGMEM
const char *str_tab[] = {
s1, s2, s3, s4, s5, s6, s7, s8, s9, s10,
s11,s12,s13,s14,s15,s16,s17,s18,s19,s20 };
const int Number_of_fortunes=20;
#define Longest_fort 100 // buffer size for messages

unsigned long count=0;
const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 13; // the number of the LED pin

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println(F("Hello, World!"));
// Pin 13 has an LED connected on most Arduino boards:
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // set the LED off
pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH); // set pullup on
}

void loop() {
// put your main code here, to run repeatedly:
if (BUTTON_PUSHED()) {
digitalWrite(ledPin, HIGH); // button feedback
say_it();
}
while (BUTTON_PUSHED()) continue; // wait for button up
delay(50); // delay to debounce
digitalWrite(ledPin, LOW); // set the LED off
count++;
}

void say_it() {
int i;
char str[Longest_fort]; // temp storage for fortune
strcpy_P(str, str_tab[count % Number_of_fortunes]);
Serial.println(str); // print from temp storage
}

int BUTTON_PUSHED() {
if (digitalRead(buttonPin) == LOW) // is button pulling down?
return 1; // YES, it is pushed
return(0); // nope
}

@Jack
I definitely want to keep it simple, so a memory card isn't the answer at this point. But as you say, it is a consideration for a future, more ambitious, revision. My favorite fortune was always "Turn over for fortune." of course, this was printed on both sides, lol.

@ JEDtoo
I must say, I wasn't expecting anyone to take the time to code a, full blown, working example. Thank you for that.

I am glad to see that I was on the right track with the use of PROGMEM and flash memory. I wasn't sure of the proper syntax and your example really helped me to see how it all works together.
I altered the code to output to LCD and eliminated the LED feedback, I'll revisit pin 13 later to control the LCD backlight. Now I just need to work on figuring out how to get the long text strings to properly wrap to the next line. The simple solution would be to scroll the text, but I like like the idea of wrapping it.

//Electronic "Fortune Cookie" 001

// Written by JEDtoo on the arduino.cc forum on 1 March 2012
// as a demo for program memory.
// Use at your own risk - no performance at any task is implied.
// If your girlfriend doesn't like her "fortune" that is your
// business, not mine.
//
// Modified by xJaymz to suit purposes on 6 March 2012

#include <LiquidCrystal.h>
#include <avr/pgmspace.h>

prog_char s1[] PROGMEM = "Test Fortune 1 Long String";
prog_char s2[] PROGMEM = "Test Fortune 2 Long String";
prog_char s3[] PROGMEM = "Test Fortune 3 Long String";
prog_char s4[] PROGMEM = "Test Fortune 4 Long String";
prog_char s5[] PROGMEM = "Test Fortune 5 Long String";
prog_char s6[] PROGMEM = "Test Fortune 6 Long String";
prog_char s7[] PROGMEM = "Test Fortune 7 Long String";
prog_char s8[] PROGMEM = "Test Fortune 8 Long String";
prog_char s9[] PROGMEM = "Test Fortune 9 Long String";
prog_char s10[] PROGMEM = "Test Fortune 10 Long String";
prog_char s11[] PROGMEM = "Test Fortune 11 Long String";
prog_char s12[] PROGMEM = "Test Fortune 12 Long String";
prog_char s13[] PROGMEM = "Test Fortune 13 Long String";
prog_char s14[] PROGMEM = "Test Fortune 14 Long String";
prog_char s15[] PROGMEM = "Test Fortune 15 Long String";
prog_char s16[] PROGMEM = "Test Fortune 16 Long String";
prog_char s17[] PROGMEM = "Test Fortune 17 Long String";
prog_char s18[] PROGMEM = "Test Fortune 18 Long String";
prog_char s19[] PROGMEM = "Test Fortune 19 Long String";
prog_char s20[] PROGMEM = "Test Fortune 20 Long String";

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// this table is in ordinary memory but has pointers
// that reference the flash PROGMEM
const char *str_tab[] = {
s1, s2, s3, s4, s5, s6, s7, s8, s9, s10,
s11,s12,s13,s14,s15,s16,s17,s18,s19,s20 };
const int Number_of_fortunes=20;
#define Longest_fort 100 // buffer size for messages

unsigned long count=0;
const int buttonPin = 6; // the number of the pushbutton pin

void setup() {
// set up the LCD's number of columns and rows
lcd.begin(16, 2);
// Print a message to the LCD
lcd.print("Welcome Message");
pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH); // set pullup on
}

void loop() {
if (BUTTON_PUSHED()) {
lcd.clear();
say_it();
}
while (BUTTON_PUSHED()) continue; // wait for button up
delay(50); // delay to debounce
count++;
}

void say_it() {
int i;
char str[Longest_fort]; // temp storage for fortune
strcpy_P(str, str_tab[count % Number_of_fortunes]);
lcd.print(str); // print from temp storage
}

int BUTTON_PUSHED() {
if (digitalRead(buttonPin) == LOW) // is button pulling down?
return 1; // YES, it is pushed
return(0); // NOPE
}

Any advice?

Thanks
James

Any advice?

Think out a good design, be the computer and draw pen/paper how it should work

Or, what is the real question?

I made somehting similar a while back as one of my first Arduino projects.
Recently I made a bigger version I called the Lucky Panda Fortune Teller.
I programmed my fortunes into the memory as well.
I also added a thermal printer so the user could take their fortune home with them.

Good luck.

robtillaart:

Any advice?

Think out a good design, be the computer and draw pen/paper how it should work

Or, what is the real question?

Sorry, somehow "Any advice?" ended up out of place. My request for advice is regarding how to get the text to automatically wrap to the second row of the lcd. I think understand how to achieve this by using setCursor if i simply wanted to display a static message, but I'm not sure how I would incorporate that into the programming with respect to the random strings. Can I define the cursor position for each string that exceeds the line length?

@Pauly

Great project! You took it waaay to the next level. Great inspiration, and plenty of code example to study and learn from.

Thanks

All you need do is split the fortune strings into sections for the display. To make it apply to different display sizes, use variables for the shape:

void setup() {
// set up the LCD's number of columns and rows
#define LCD_WIDE 20
#define LCD_LINES 4
lcd.begin(LCD_WIDE, LCD_LINES);

Then, in the say_it routine, use the variables defined above:

void say_it() {
int thisrow;
char str[Longest_fort], str2[LCD_WIDE+1]; // temp storage for fortune
lcd.clear();
strcpy_P(str, str_tab[count % Number_of_fortunes]); // copy into working memory
for (thisrow=0; // the lesser of (string lines,LCD_LINES) times through
thisrow<=min(((strlen(str)-1)/LCD_WIDE),LCD_LINES-1);
thisrow++) {
lcd.setCursor(0,thisrow); // set cursor to start of this row
delay(50); // sometimes setCursor is slow
// copy substring for this line, from start
// to lesser of LCD line length or end of string
strncpy(str2,&str[thisrowLCD_WIDE],
min(LCD_WIDE,strlen(str)+1-(thisrow
LCD_WIDE)));
str2[LCD_WIDE]=0; // null terminate substring
lcd.print(str2); // display it
delay(10);
}
}

FYI: min() returns the lesser of the values. It often makes for clearer code. We want to loop for the number of lines on the display OR for the number of lines of text that we have, whichever is less. We want to print either a whole line OR the portion of the string that is left, whichever is less.

Our strings are done as an array of characters instead of the Arduino string object new to version 0019 and later. As such, the string library routines can't be used but the C library routines can be. strncpy is from those. There is not a strncpy_P in pgmspace.h, which would have let us just move the lines directly into the str2[] buffer and skip the str[] buffer completely.

This should work for you.

One fix: I noticed the button wasn't performing quite as expected and found the problem.

In the loop(), the debounce stuff (while.. and delay) is supposed to be inside the if instead of outside. Otherwise, it can ignore some button pushes.
Change it to:

void loop() {
if (BUTTON_PUSHED()) {
say_it();
while (BUTTON_PUSHED()) continue; // wait for button up
delay(50); // delay to debounce
}
count++;
}