[Raspberry Pi General] harvey7:
This program is written in C and imports a few python scripts for LCD related actions(a few scripts provided by Adafruit). Basically, the program extends features of the classic Catchphrase Electronic game by allowing a variable number of categories and words for each. An overall synopsis of methods used to drive this program and device are posix threads, signals, PWM, gpio, multi-dimensional array allocation, unix directory commands, and more. Furthermore, the directory of word files is opened (each file formatted as "CATEGORY_.txt"), and the names of each text file (minus the underscore and extension) are loaded in as categories. Next, a three dimensional array is allocated to hold each category and its corresponding words loaded in as strings.

The code is thoroughly commented, and is hopefully understandable. The hardware used was an Adafruit LCD(i2c), four momentary push-button switches, Raspberry Pi, 3V (300-500Hz rated) buzzer, and enclosure (no rechargeable battery added because of the extra money involved but could easily be used as well). The rules are very similar to that of the normal game. During the timer sequence (~55s) when team-mates are guessing a word, pulse width modulation is used to simulate a 400Hz square wave with a 15s period and 50% duty cycle (calculation used to achieve this explained in code). This value is changed over the timer interval to increase the speed of the pulsing to indicate nearing the end of the sequence. Honestly, it probably would have been better to separate some of the functions into other modules, but I decided against it for this release for simplicity sake.

I hope this may be useful for someone interested in creating their own RasPiPhrase device. If you notice any bugs or issues please let me know and I will correct it. The necessary external libraries are wiringPi and python2.7-dev, the rest are a part of unix/C. A makefile that I wrote is included (in the attached zip) as well as all necessary code related files. Some example word files are included too, but you can drop in as many as you want and modify them however you want. The code takes care of the rest. This is the first version and has been tested, though not extensively. Let me know if you have any questions about the code. Enjoy! ;)

-Chris

/*
============================================================================
Name : RasPiPhrase.c
Author : Christopher B. Harvey
Version : 1.0
Description : Top-level part of program that completes initial setup,
* waits during game, and then cleans up resources at the end.
============================================================================
*/

#include "/usr/include/python2.7/Python.h"
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <wiringPi.h>
#include <malloc.h>
#include <time.h>
#include <string.h>
#include <sys/resource.h>
#include <math.h>
#include <signal.h>
#include <pthread.h>
#include <dirent.h>
#include "lcd.h"

//gpio pin assignments
#define NUM_CAT 3 //number of categories not including "Everything" (modify if add more)
#define BUZZER 1 //buzzer pin for gpio
#define CAT_NXT 6 //category and next switch pin for gpio
#define START 0 //START and STOP switch pin for gpio
#define TEAM_ONE 12 //team one score switch pin for gpio
#define TEAM_TWO 13 //team two score switch pin for gpio

//for importing python script
PyObject *pName, *pModule, *pDict, *pFunc1, *pFunc2, *pFunc3, *pFunc4, *pVal;

//number of files found in words directory
//and therefore the number of categories
int num_fnames=0;
int num_cat=0;

//currently selected category
//default is EVERYTHING
int category_g=0;

//integer array holding number of strings in each category
int *num_strcat=NULL;

//array of strings holding filenames
char **fname_arr=NULL;

//array of category names
char **cat_arr=NULL;

//three dimensional array of strings
//to hold arrays of each category's words
//Example: meta_arr[1][2] would be the third string of the first
// actual category(index 0 holds empy EVERYTHING category)
char ***meta_arr=NULL;

//string to hold msg to be output to lcd
char *lcd_str=NULL;

//one character strings(plus null) to hold converted integer scores
char *t1=NULL;
char *t2=NULL;

//child thread for buzzer timer
pthread_t tim_thread;
pthread_attr_t attr;//attribute for thread stack size

//vars to hold pwm calculations
float clkdiv=0;
float range_per=0;

//veriables for switching between
//different sections of interrupt functions
int irupt1=0;
int irupt2=0;
int irupt3=0;

//vars for keeping up with scores
int team1=0;
int team2=0;

//prototypes
void load_words(void);
void display_word(void);
int get_numstr(char *filename);
int get_rand(int numof);
void rm_fext(char *fname);
void load_fnames_cat(char *dirstr);
void print_cat(void);
void menu(void);
void sighand_timer(int sig);
float clk_div(float freq);
float range(float freq, float period);
void *timer_buzz(void *ptr);
void change_cat_point(void);
void team_one_p(void);
void team_two_p(void);
void start_buzz_stop(void);
void assign_point_buzz(void);
void debounce_sw(int pin);

int main(void) {

//local var declarations
char c;
int i,b,nstr=0;

//load filenames and categories into arrays
load_fnames_cat("./words");

//set 256Mb for stack size to be allocated when
//pthread_create is called for timer thread
size_t stk_alloc = 256*1024*1024;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, stk_alloc);//prevents stack overflow issues

//allocate memory for lcd msg strings and score strings
lcd_str=(char*)malloc(50*sizeof(char));
t1=(char*)malloc(3*sizeof(char));
t2=(char*)malloc(3*sizeof(char));

//load word lists from all proper files in words directory into memory
load_words();

//seed random number generator
srand(time(0));

//start python interpretation
Py_Initialize();
PyEval_InitThreads();

//initialize wiringPi
if (wiringPiSetup() < 0) {
fprintf(stderr, "ERROR! WiringPi was unable to start: %s\n", strerror(errno));
return 1;
}

//set buttons as input
pinMode(CAT_NXT,INPUT);
pinMode(START,INPUT);
pinMode(TEAM_ONE,INPUT);
pinMode(TEAM_TWO,INPUT);

//set all buttons to pull down
pullUpDnControl(CAT_NXT,PUD_DOWN);
pullUpDnControl(START,PUD_DOWN);
pullUpDnControl(TEAM_ONE,PUD_DOWN);
pullUpDnControl(TEAM_TWO,PUD_DOWN);

//load python script file
SCRIPT_LOAD("lcd_funcs");

//startup sequence
LCD_INIT();
LCD_OUT("RasPiPhrase\n Version 1.0");
sleep(2);
LCD_OUT(" Created by\nChris B. Harvey");
sleep(2);
LCD_CLEAR();

//set interrupts
wiringPiISR(CAT_NXT, INT_EDGE_FALLING,change_cat_point);
wiringPiISR(START,INT_EDGE_FALLING,start_buzz_stop);
wiringPiISR(TEAM_ONE, INT_EDGE_FALLING, team_one_p);
wiringPiISR(TEAM_TWO, INT_EDGE_FALLING, team_two_p);

//start game by loading category select menu
menu();

scanf("%c",&c);//wait for key input to end

LCD_CLEAR();
LCD_BLIGHT(OFF);

//dereference python objects
CLEAN_PY();

//end python interpretation
Py_Finalize();

//properly free all dynamically allocated memory
for(i=0;i<num_cat;i++){
nstr=num_strcat[i];
for(b=0;b<nstr;b++){
free(meta_arr[i][b]);
}
free(meta_arr[i]);
}
free(meta_arr);
free(lcd_str);
free(t1);
free(t2);

return 0;
}

//allocate space for string arrays and load file names and categories
//file format should be CATEGORY_.txt
void load_fnames_cat(char *dirstr){
int prev_mem=0,cur_mem=0;

//directory related declarations
DIR *word_direc;
struct dirent *dir;

//open the supplied directory
word_direc = opendir(dirstr);

//if successful read in the directory
if (word_direc){
//inc vars for first allocation
num_fnames++;
cur_mem=18+prev_mem;

//initial allocation for "dummy" EVERYTHING category
fname_arr=realloc(fname_arr,sizeof(char*)*num_fnames);
fname_arr[num_fnames-1]=realloc(fname_arr[num_fnames-1],sizeof(char)*cur_mem);
cat_arr=realloc(cat_arr,sizeof(char*)*num_fnames);
cat_arr[num_fnames-1]=realloc(cat_arr[num_fnames-1],sizeof(char)*cur_mem);

//set vars
prev_mem=cur_mem;
strcpy(fname_arr[num_fnames-1],"EVERYTHING_.txt");//doesn't exist just helps with coding clarity
strcpy(cat_arr[num_fnames-1],"EVERYTHING");

//allocate array of strings for filenames and categories
//and place them into the arrays
while ((dir=readdir(word_direc))!=NULL){
//attempt to insure only correct files are selected and temp files/others are ignored
if((strlen(dir->d_name)>2)&&(rindex(dir->d_name,'_')!=NULL)&&(rindex(dir->d_name,'~')==NULL)){
//inc vars
num_fnames++;
cur_mem=18+prev_mem;

//allocate array mem
fname_arr=realloc(fname_arr,sizeof(char*)*num_fnames);
fname_arr[num_fnames-1]=realloc(fname_arr[num_fnames-1],sizeof(char)*cur_mem);
cat_arr=realloc(cat_arr,sizeof(char*)*num_fnames);
cat_arr[num_fnames-1]=realloc(cat_arr[num_fnames-1],sizeof(char)*cur_mem);

//set vars
prev_mem=cur_mem;
strcpy(fname_arr[num_fnames-1],(dir->d_name));
strcpy(cat_arr[num_fnames-1],fname_arr[num_fnames-1]);

//remove file extension and underscore from filenames
rm_fext(cat_arr[num_fnames-1]);
}
}
//clean up
closedir(word_direc);

//set number of categories
num_cat=num_fnames;
}
}

void load_words(void){
FILE *fp;
int i,b,temp_strnum=0;
char dir[]="./words/";
char str_pathfile[40];

//allocate space for each category for both
meta_arr=(char***)malloc(sizeof(char**)*num_cat);
num_strcat=(int*)malloc(sizeof(int)*num_cat);

for(i=0;i<num_cat;i++){
if(i==0){
//blank for everything category
meta_arr[i]=(char**)malloc(sizeof(char*)*1);
meta_arr[i][i]=(char*)malloc(1*sizeof(char));
meta_arr[i][i][i]='\0';
num_strcat[0]=1;//doesnt matter (EVERYTHING category)
}
else{
//format string to be path to file
strcpy(str_pathfile,dir);
strcat(str_pathfile,fname_arr[i]);
temp_strnum=get_numstr(str_pathfile);

//set number of strings for current category
num_strcat[i]=temp_strnum;

//open file for reading
fp=fopen(str_pathfile,"r");

//allocate memory for pointer to each string and each string itself
meta_arr[i]=(char**)malloc(sizeof(char*)*temp_strnum);
for(b=0;b<temp_strnum;b++){
meta_arr[i][b]=(char*)malloc(16*sizeof(char));

//load in word from file into memory
if((fscanf(fp,"%s",meta_arr[i][b]))!=1){
printf("\n\nERROR loading\nwords from file!\n");
sleep(2);
exit(1);
}
}
fclose(fp);
}

}

}

//output string constructed from current word
//as well as converted team scores
void display_word(void){
char *str=NULL;
int tmp_rand=0;//for use in recursion for everything category

//convert integer score to character
//and put into the correct string
t1[0]=(char)(((int)'0')+team1);
t2[0]=(char)(((int)'0')+team2);
t1[1]='\0';
t2[1]='\0';

if(category_g!=0){
//randomly select string
str=meta_arr[category_g][get_rand(num_strcat[category_g])];

}
else{
//randomly select category and string for EVERYTHING
tmp_rand=get_rand(num_cat);
while(tmp_rand==0){
tmp_rand=get_rand(num_cat);
}
str=meta_arr[tmp_rand][get_rand(num_strcat[tmp_rand])];
}

//construct string from scores and word then output to lcd
strcpy(lcd_str,str);
strcat(lcd_str,"\nTEAM1:");
strcat(lcd_str,t1);
strcat(lcd_str," TEAM2:");
strcat(lcd_str,t2);
LCD_OUT(lcd_str);

}

//prints string constructed from current category and each team's score
void print_cat(void){

//convert ints to characters and put into string
t1[0]=(char)(((int)'0')+team1);
t2[0]=(char)(((int)'0')+team2);
t1[1]='\0';
t2[1]='\0';

//construct string from scores and category name then output to lcd
strcpy(lcd_str,cat_arr[category_g]);
strcat(lcd_str,"\nTEAM1:");
strcat(lcd_str,t1);
strcat(lcd_str," TEAM2:");
strcat(lcd_str,t2);
LCD_OUT(lcd_str);
}

//opens the specified file and
//counts the number of strings
int get_numstr(char *filename){
int fsize=0;
char *string=NULL;
string=(char*)malloc(20*sizeof(char));
FILE *fp;
fp=fopen(filename,"r");
while(fscanf(fp,"%s",string)==1){
fsize++;
}
fclose(fp);
free(string);
return fsize;
}

//gets a random number within range
//specified by number of items
int get_rand(int numof){
int random=rand()%numof;
return random;
}

//function to remove file extension and underscore
void rm_fext(char *fname){
char *ptr_todot=rindex(fname,'_');//find memory where the dot is stored
if(ptr_todot!=NULL){
(*ptr_todot)='\0';//replace with terminator to basically remove extension
}
}

//category select menu
void menu(void){
category_g=0;
LCD_OUT("Please select\n a category.")
sleep(1);
print_cat();
irupt1=1;

}

//function to debounce button on supplied pin
void debounce_sw(int pin){
int cnt=0,wait=1;
while(wait){
usleep(1000);//sleep 10ms
if(digitalRead(pin)==1){
cnt=0;
//continue to wait for bouncing to stop
}
else{
cnt++;
}
if(cnt>=10){
//we can now end loop
wait=0;
}
}
}

//interrupt function for when the start/stop button is pressed
void start_buzz_stop(void){
debounce_sw(START);

//set start/stop button to pulldown
pullUpDnControl(START,PUD_DOWN);
if(irupt1){
irupt1=0;
pthread_create(&tim_thread,&attr,timer_buzz,NULL);
}
else if(irupt2){
irupt2=0;
pthread_kill(tim_thread,SIGUSR1);
pthread_join(tim_thread,NULL);//make sure to wait until child thread terminates
pwmWrite(BUZZER,0);
digitalWrite(BUZZER, LOW);
menu();
}
}

//interrupt function for when the category/next button is pressed
void change_cat_point(void){
debounce_sw(CAT_NXT);

//set category/next button to pull down
pullUpDnControl(CAT_NXT,PUD_DOWN);
if(irupt1){
category_g++;
if(category_g>=num_cat){
category_g=0;//wrap back around
}
print_cat();
}
else if(irupt2){
irupt2=0;
pthread_kill(tim_thread,SIGUSR1);
pthread_join(tim_thread,NULL);//make sure to wait until child thread terminates
pwmWrite(BUZZER,0);
digitalWrite(BUZZER, LOW);
LCD_OUT("Assign point to\nthe proper team.");
irupt3=1;
}
}

//function to assign point after timer expires
void assign_point_buzz(void) {
irupt2=0;
LCD_OUT("Time expired!!!!");
pthread_join(tim_thread,NULL);//make sure to wait until child thread terminates
sleep(1);
LCD_OUT("Assign point to\nthe proper team.");
irupt3=1;
}

//interrupt function to give point when team one button is pressed
void team_one_p(void){
debounce_sw(TEAM_ONE);

//set pull down for score buttons
pullUpDnControl(TEAM_ONE,PUD_DOWN);
pullUpDnControl(TEAM_TWO,PUD_DOWN);

if(irupt3){
irupt3=0;
team1++;
if(team1>=7){
LCD_OUT(" TEAM1 WINS!!!");
sleep(2);
team1=0;
team2=0;
}
menu();
}
}

//interrupt function to give point when team two button is pressed
void team_two_p(void){
debounce_sw(TEAM_TWO);

//set pull down for score buttons
pullUpDnControl(TEAM_ONE,PUD_DOWN);
pullUpDnControl(TEAM_TWO,PUD_DOWN);

if(irupt3){
irupt3=0;
team2++;
if(team2>=7){
LCD_OUT(" TEAM2 WINS!!!");
sleep(2);
team1=0;
team2=0;
}
menu();
}
}

//signal handler for SIGUSR1 signal when
//sent to timer child thread
void sighand_timer(int sig){
pthread_exit(0);//end child thread
}

//function called by child thread to begin timer and buzzer sequence
void *timer_buzz(void *ptr) {
signal(SIGUSR1,sighand_timer);//call function when SIGUSR1 is caught
irupt1=0;
irupt2=1;
display_word();
clk_div(400); //400 Hz, clock_div func supplies correct value
range(400, 15); //15s Period, range func supplies correct value
pinMode(BUZZER, PWM_OUTPUT);

//and set its value to 0
pwmSetMode(PWM_MODE_MS);
pwmWrite(BUZZER, 0);
digitalWrite(BUZZER, LOW);

//bring frequency to 300Hz
//originally 19.2MHz PWM Clock
pwmSetClock(clkdiv);
pwmSetRange(range_per);

//begin pwm writing sequency to produce
//square waves that mimic the catchphrase timer
pwmWrite(BUZZER, (range_per * .25));
sleep(12);
pwmSetRange(range(400, 12));
pwmWrite(BUZZER, (range_per * .50));
sleep(10);
pwmSetRange(range(400, 10));
pwmWrite(BUZZER, (range_per * .75));
sleep(10);
pwmSetRange(range(400, 8));
pwmWrite(BUZZER, (range_per * .90));
sleep(10);
pwmSetRange(range(400, 6));
pwmWrite(BUZZER, (range_per * .90));
sleep(10);
pwmSetClock(clk_div(300));
pwmSetRange(range(300, 15));
pwmWrite(BUZZER, range_per);
sleep(1);
assign_point_buzz();
sleep(1);
pwmWrite(BUZZER, 0);
digitalWrite(BUZZER, LOW);
return 0;
}

//calculates pwm clock divisor for a frequency in Hz
float clk_div(float freq) {
clkdiv = 19.2 * (pow(10, 6)) / freq;
return clkdiv;
}

//calculates pwm range for a period in seconds and a frequency in Hz
float range(float freq, float period) {
range_per = period / (1 / freq);
return range_per;
}

/*
============================================================================
Name : lcd.h
Author : Christopher B. Harvey
Version : 1.0
Description : Header that contains function-like macro definitions
* for lcd related tasks through use of python scripts.
============================================================================
*/

//functionality defines for lcd backlight function
#define ON 1
#define OFF 0

#define SCRIPT_LOAD(pyfile) \
pName = PyString_FromString(pyfile); \
PyRun_SimpleString("import sys"); \
PyRun_SimpleString("sys.path.append(\"/home/rasppi/src/py_scripts\")"); \
pModule = PyImport_Import(pName); \
pDict = PyModule_GetDict(pModule); \

#define LCD_INIT() \
pFunc1 = PyDict_GetItemString(pDict, "lcdinit"); \
if (PyCallable_Check(pFunc1)){ \
PyObject_CallObject(pFunc1, NULL); \
} else { \
PyErr_Print(); } \

#define LCD_OUT(stri) \
pFunc2 = PyDict_GetItemString(pDict, "lcdout"); \
if (PyCallable_Check(pFunc2)){ \
PyObject_CallFunction(pFunc2, "s", stri); \
} else { \
PyErr_Print(); } \

#define LCD_BLIGHT(i) \
pFunc3 = PyDict_GetItemString(pDict, "lcdbacklight"); \
if (PyCallable_Check(pFunc3)){ \
PyObject_CallFunction(pFunc3, "i", i); \
} else { \
PyErr_Print(); } \

#define LCD_CLEAR() \
pFunc4 = PyDict_GetItemString(pDict, "lcdclear"); \
if (PyCallable_Check(pFunc4)){ \
PyObject_CallObject(pFunc4, NULL); \
} else { \
PyErr_Print(); } \

#define CLEAN_PY() \
Py_DECREF(pModule); \
Py_DECREF(pName); \