TIGCC Programming Lessons
The following is part of a series of lessons designed to help teach
people to program in C for the TI-89, 92+, and V200 calculators
using the TIGCC development environment.
If you wish, you can download the program source code, project
files, and binaries
here.
Lesson 10: File I/O
Step 1 - Introduction to File I/O
One of the more important concepts later in programming is the ability
to store information even after the program has terminated. This
technique has many uses, such as saving hi score information or level
data for a game.
Storing data outside the program has many advantages: small program
size, modularity, persistent data retrieval. For example, imagine if
you had a one player game, and that game saved your name along with your
score. Now, each time you start the game, you must enter your name,
because the program doesn't remember things like this.
Well, what could we do to solve this program. Well, we could use a
global variable whose value is embedded inside the program. That's
probably not a bad idea for small sets of data, but what about giant
sets of data? We don't want to add 4K to our program size, especially on
a system whose memory is tight enough as it is. Furthermore, we have the
added limitation that all programs on the TI-89/92+/V200 must be smaller
than 64K. This is the largest size a program can be. It's a limitation
of the system. Even if we compress the program, the program must be
smaller than 64K when decompressed. However, we can load as much data as
we have space for into memory once our program is started. Finally, we
can take advantage of the fact that our FlashROM space is readable, but
not executable. So, we cannot run programs in this space1,
but we can store data there.
Okay, we know that we can store data outside the program, and we know
that we have lots of extra space outside the program, how do we take
advantage of that. File I/O of course. Instead of storing data inside
the program file, we will create another file in which to store data.
Thanks to Zeljko Juric, we will soon see how easy this is.
1. Programs that are executed from FlashROM are actually copied into
our RAM memory and run from there, then deleted once the program
stops running.
Step 2 - A Short Example Using File I/O
Start TIGCC and begin a new project. Name the source file fileio and
save the project. Edit the file to look like this:
fileio.c
// comment this next line out for TI-92+/V200 support
#define USE_TI89
#ifndef USE_TI89
#define USE_TI92PLUS
#define USE_V200
#endif
#include <dialogs.h>
#include <ctype.h>
#include <string.h>
#include <mem.h>
#include <stdio.h>
#include <kbd.h>
#include <stdlib.h>
#include <alloc.h>
#include <args.h>
#include <menus.h>
// the minus and the negative sign have different values, though they look nearly identical
#define DASH 173
#ifdef USE_TI89
enum DialogConstants {DLG_WIDTH = 140, DLG_HEIGHT = 85};
#else
enum DialogConstants {DLG_WIDTH = 200, DLG_HEIGHT = 90};
#endif
enum ErrorConstants {FILEIO1,FILEIO2,FILEIO3,FILEIO4,FILEIO5,MEMERR1,MEMERR2,
DATA_ERROR,DOB_ERROR,HEIGHT_ERROR,WEIGHT_ERROR};
enum MenuConstants {MENU_DATA = 1000,MENU_READ,MENU_WRITE,MENU_PRINT,MENU_EXIT};
const char *error[] = {"File I/O Error","Unable to Open File!",
"Unable to read or write data to file",
"File I/O Status","Success!",
"Memory Error","Not enough free memory!",
"Data Error","DOB Must be MM-DD-YYYY",
"Height must be between 100 and 250 cm",
"Weight must be between 35 and 180 kg"};
typedef struct {
char name[15];
short int height;
short int weight;
char dob[11];
} PERSON;
inline void dlgError(short int title, short int msg) {
DlgMessage((char *)error[title],(char *)error[msg],BT_OK,BT_NONE);
}
// check for valid date of birth (MM-DD-YYYY)
short int isValidDOB(const unsigned char *dob) {
if (isdigit(dob[0]) && isdigit(dob[1]) && (dob[2] == '-' || dob[2] == DASH) &&
isdigit(dob[3]) && isdigit(dob[4]) && (dob[5] == '-' || dob[5] == DASH) &&
isdigit(dob[6]) && isdigit(dob[7]) && isdigit(dob[8]) && isdigit(dob[9])) {
return TRUE;
}
return FALSE;
}
// check for valid height (in centimeters)
short int isValidHeight(const short int height) {
if (height > 100 && height < 250) {
return TRUE;
}
return FALSE;
}
// check for valid weight (in kilograms)
short int isValidWeight(const short int weight) {
if (weight > 35 && weight < 180) {
return TRUE;
}
return FALSE;
}
void initPerson(PERSON *p) {
// terminate the person strings
p->name[0] = 0;
p->dob[0] = 0;
// enter generic person information
strcpy(p->name,"Dominic Silver");
strcpy(p->dob,"06-02-1972");
p->height = 190;
p->weight = 87;
}
void formatRequestString(char *temp, PERSON *p) {
// erase the buffer string
memset(temp,0,34*sizeof(char));
// format the buffer string so the dialog box will have default values
sprintf(temp,"%-15s%-11s%03hd %03hd",p->name,p->dob,p->height,p->weight);
// add string separators
temp[14] = 0;
temp[25] = 0;
temp[29] = 0;
temp[33] = 0;
}
short int getData(PERSON *p, char *buffer) {
HANDLE dlg = H_NULL;
int done = FALSE;
char *token;
// create the dialog box
if ((dlg = DialogNewSimple(DLG_WIDTH,DLG_HEIGHT)) != H_NULL) {
// format the dialog box
DialogAddTitle(dlg,"Personal Information",BT_OK,BT_NONE);
DialogAddRequest(dlg,5,15,"Name:",0,14,18);
DialogAddRequest(dlg,5,25,"DOB:",15,10,18);
DialogAddRequest(dlg,5,35,"Height (cm):",26,3,5);
DialogAddRequest(dlg,5,45,"Weight (kg):",30,3,5);
while (!done) {
// loop until the user presses ENTER
while (DialogDo(dlg,CENTER,CENTER,buffer,NULL) != KEY_ENTER);
// grab the name from the string buffer
token = buffer;
p->name[0] = 0;
strcpy(p->name,token);
// grab the DOB from the string buffer
token = buffer + 15;
p->dob[0] = 0;
strcpy(p->dob,token);
// grab the height from the string buffer
token += 11;
p->height = atoi(token);
// grab the weight from the string buffer
token += 4;
p->weight = atoi(token);
// we're done unless we fail one of our validity tests
done = TRUE;
// check for valid DOB entry (MM/DD/YYYY)
if (!isValidDOB((const unsigned char *)p->dob)) {
dlgError(DATA_ERROR,DOB_ERROR);
done = FALSE;
}
// check for reasonable valid height
if (!isValidHeight((const short int)p->height)) {
dlgError(DATA_ERROR,HEIGHT_ERROR);
done = FALSE;
}
// check for reasonable valid weight
if (!isValidWeight((const short int)p->weight)) {
dlgError(DATA_ERROR,WEIGHT_ERROR);
done = FALSE;
}
}
// free the memory used by the dialog
HeapFree(dlg);
} else {
dlgError(MEMERR1,MEMERR2);
return FALSE;
}
return TRUE;
}
short int writeData(PERSON *p) {
FILE *f = NULL;
short int fileio = TRUE;
// open file for writing
if ((f = fopen("TESTFILE","wb")) == NULL) {
dlgError(FILEIO1,FILEIO2);
fileio = FALSE;
} else {
// write structure data to the file
if (fwrite(p,sizeof(PERSON),1,f) != 1) {
dlgError(FILEIO1,FILEIO3);
fileio = FALSE;
}
// append the file ID tag
fputc(0,f);
fputs("FIO",f);
fputc(0,f);
fputc(OTH_TAG,f);
// close the file
fclose(f);
}
return fileio;
}
short int readData(PERSON *p) {
FILE *f = NULL;
short int fileio = TRUE;
// open file for reading in binary mode
if ((f = fopen("TESTFILE","rb")) == NULL) {
dlgError(FILEIO1,FILEIO2);
fileio = FALSE;
} else {
// read data from file into PERSON structure
if (fread(p,sizeof(PERSON),1,f) != 1) {
dlgError(FILEIO1,FILEIO3);
fileio = FALSE;
}
// close the file
fclose(f);
}
return fileio;
}
void printData(PERSON *p) {
char name[25],dob[20],height[20],weight[20];
HANDLE dlg = H_NULL;
if ((dlg = DialogNewSimple(DLG_WIDTH,DLG_HEIGHT)) != H_NULL) {
// create the personal information strings
sprintf(name,"Name: %s",p->name);
sprintf(dob,"DOB: %s",p->dob);
sprintf(height,"Height: %hd cm",p->height);
sprintf(weight,"Weight: %hd kg",p->weight);
// format the dialog box
DialogAddTitle(dlg,"Personal Information",BT_OK,BT_NONE);
DialogAddText(dlg,5,15,name);
DialogAddText(dlg,5,25,dob);
DialogAddText(dlg,5,35,height);
DialogAddText(dlg,5,45,weight);
// display the dialog box
DialogDo(dlg,CENTER,CENTER,NULL,NULL);
HeapFree(dlg);
}
}
void _main(void) {
char buffer[34];
PERSON p;
short int done = 0, option = MENU_DATA;
HANDLE dlg = H_NULL, menu = H_NULL;
// initialize the person structure
initPerson(&p);
// format the request buffer string
formatRequestString(buffer,&p);
if ((dlg = DialogNewSimple(DLG_WIDTH,DLG_HEIGHT)) == H_NULL) {
dlgError(MEMERR1,MEMERR2);
return;
}
if ((menu = PopupNew(NULL,0)) == H_NULL) {
dlgError(MEMERR1,MEMERR2);
HeapFree(dlg);
return;
}
// create Dialog Menu
PopupAddText(menu,-1,"Enter new Data",MENU_DATA);
PopupAddText(menu,-1,"Read Data from File",MENU_READ);
PopupAddText(menu,-1,"Save Data to File",MENU_WRITE);
PopupAddText(menu,-1,"Print Information",MENU_PRINT);
PopupAddText(menu,-1,"Exit",MENU_EXIT);
DialogAddTitle(dlg,"Main Menu",BT_OK,BT_CANCEL);
DialogAddPulldown(dlg,5,15,"Selection:",menu,0);
do {
if (DialogDo(dlg,CENTER,CENTER,NULL,&option) != KEY_ENTER) {
option = MENU_EXIT;
}
switch (option) {
case MENU_DATA:
getData(&p,buffer);
printData(&p);
break;
case MENU_READ:
if (readData(&p)) {
dlgError(FILEIO4,FILEIO5);
formatRequestString(buffer,&p);
printData(&p);
}
break;
case MENU_WRITE:
if (writeData(&p)) {
dlgError(FILEIO4,FILEIO5);
}
break;
case MENU_PRINT: printData(&p); break;
case MENU_EXIT: done = TRUE; break;
}
} while (!done);
// free the memory used by the dialog box and selector menu
HeapFree(menu);
HeapFree(dlg);
}
Step 2a - Compile and run the Program
Before we build this example, we need to make an adjustment to the
program options. We did this once before in lesson 3 to set the MIN_AMS
version. Goto Project, Options, Compilation Tab, Program Options,
Calculator. Deselect all three calculators. The calculator build
selection is taken care of with the USE_TI89 or USE_TI92PLUS USE_V200
definitions.
Build the binary for your calculator. Note that you will need to comment
out the top #define USE_TI89 line if you are compiling the TI-92+/V200
version. Due to differences in screen size and fonts used in dialog
boxes, I had to make separate programs. The programs are identical
except for dialog box sizes. Send the file to TiEmu and run it. It will
look something like this:
Continue with Part II
|