<$ title="Stamp Project of the Month"; include("template.inc"); ?>

Basic Stamp Circuit of the Month

Here's where I'll try to show you something we've done with Basic Stamps this month. Always quick and dirty, and the month is plus or minus a few days, OK? These circuits are easy to try with our economical ASP-III and even easier with the companion lab kit.

This Month's News

bullet Bonus Project: A New Basic-Like Compiler (try it online for free)
bulletSign up for our new FREE quarterly microprocessor industry newsletter!
bulletTired of Basic? New Microcontroller C Programming Kit and Tutorial
bulletNew Tutorial Site
bulletParallax now has all past Stamp Projects of the Month available

horizontal rule

The Stamp Project of the month is on indefinite hiatus.

December 2004

Configuring C

Well, a late project this month, and not really about Basic Stamps. Maybe I should change the name to "Microcontroller Project of the Month". Keep in mind I don't post to the Stamp List when the project isn't Stamp related, so if you want to always keep up to date, consider subscribing to the RSS feed by clicking the little RSS box up top. If you aren't using an RSS reader, you should be! There are plenty of free ones and many browsers support RSS now.

I do a lot of projects with our APP-IV board. It isn't very expensive and I just love using the GNU C compiler. It makes projects so easy since there are well thought out C libraries for just about everything (see http://www.awce.com/app4kit.htm for more info).

It seems like just about every project I do, I want to make several "variables" that people can configure via a serial port. Sometimes I put an extra jumper on the board to enter configuration mode, or sometimes I require the user press Enter 5 seconds after the system resets. Either way, I need to collect some data and store in EEPROM. I also need to recall the data on each start up.

After writing this code about 2 or 3 times I thought there has to be a way to make something reusable out of this. I finally did.

Its All About Structure

The first step was to put all my configuration variables into a structure. For example:

struct CFGBLK
{
  unsigned char mark;
  unsigned sn;
  unsigned char delay;
  unsigned char fmt;
};
struct CFGBLK cfg;

Now when I want to use the sn variable, I have to write cfg.sn. The mark field is special and always present. I put 0xA5 in this variable so that I can read from EEPROM and see if it is empty. A fresh EEPROM won't have 0xA5 in this field.

If I do want to save the info to EEPROM, that's simple:

	cfg.mark=0xA5;
	eeprom_write_block(&cfg,(void *)0,sizeof(cfg));

How hard is that?

Now we need some code to load up the block appropriately:

void cfg_init(void)
{
  	if (eeprom_read_byte((void *)0)!=0xA5)
	{
	  // EEPROM has never been set up
	  cfg.sn=1;
	  cfg.delay=10;   
	  cfg.fmt='A';
	}
	else
	{
	   eeprom_read_block(&cfg,(void *)0,sizeof(cfg));
	}
}

This is one of the first things your code calls. It looks to see if the EEPROM contains the magic number (0xA5). If not, the program loads default values, otherwise it reads the structure back from EEPROM. Of course, if the layout of the structure changes, the load won't be good but since this is a single computer the chances of that happening are slim. On the other hand, if you change the layout during development, just add new items to the end of the structure or erase the EEPROM.

User Interface

I'm assuming you have standard I/O tied to the serial port (app4uart.c allows this). However, if you don't want to incur the overhead of printf, you can write your own routines since the code doesn't really use much formatting. You need another structure that describes the configuration variables:

struct cfgdata
  {
  char *name;
  void *var;
  unsigned char typ;
  };

The name field is the text you want to show the user, and var is a pointer to the variable. The typ field is 0 for an unsigned integer variable or 1 for an unsigned char variable. You can add other types (such as float or double) if you like.

Here's part of the configuration function:

void configure(void) 
{
    int n;
    struct cfgdata cdata[]=   // note this is on the stack, so it won't consume memory forever
	  { 
		{"Serial #", &cfg.sn, 0}, 
		{"Delay", &cfg.delay,0},
		{"Format",&cfg.fmt,1},
	  };
    while (1)
	{
		int c;
		putstr("Setup\n"); 
		putstr("A - Exit\n");
		putstr("B - Save\n");
		putstr("C - Diagnostic\n");
// generate dynamic menu (D-Z)
		for (n=0;n<sizeof(cdata)/sizeof(cdata[0]);n++)
		  {
		  putchar(n+'D');
		  putchar('-');
		  putstr(cdata[n].name);
		  putchar(':');
		  if (cdata[n].typ==0) putint(*(unsigned *)cdata[n].var);
		  if (cdata[n].typ==1) putint(*(unsigned char *)cdata[n].var);
		  putchar('\n');
		  }
		c=getchar();  // get menu choice
		c=toupper(c)-'A';
		if (c==0) return;
		if (c==1)
		{
			cfg.mark=0xA5;
			eeprom_write_block(&cfg,(void *)0,sizeof(cfg));
			putstr("OK\n");
			continue;
		}
		if (c==2)
		{
		// Do diagnostic code here
		}
		c-=3;  // convert to cdata index
		if (c<sizeof(cdata)/sizeof(cdata[0])) //don't process out of bounds
		  {
		  putstr(cdata[c].name);
		  putchar(':');
		  if (cdata[c].typ==0) *(unsigned int *)cdata[c].var=getintval();
		  if (cdata[c].typ==1) *(unsigned char *)cdata[c].var=getcharval();
		  }
    }
		
}  

The idea is that this generates a menu that says:

Setup
A - Exit
B - Save
C - Diagnostic
D - Serial # : 1
E - Delay: 100
F - Format: 65

You press a key to get the appropriate action or change the parameter. It would be easy to add new types such as characters or floating point numbers if you wanted to do that, but this version only does unsigned integers and unsigned characters (that it treats like decimal numbers). You could even make custom "types" that used hex numbers instead of decimal.

This makes it very easy to add a configuration menu to your APP-IV projects. Just call config_init early in your program and if you detect the user wants to configure the system, call configure (make sure the UART and the stdio is set up first, however). This is an easy way to make your projects more professional and flexible.

The Code

#include <avr/io.h>
#include <avr/eeprom.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "myproject.h"  // replace this -- it contains the struct CFG definition
#include "app4uart.h"
// Replace these I/O routines with whatever works for you
void putstr(char *s)
{
  while (*s) putchar(*s++);
}
void putint(unsigned int n)
{
  printf("%d",n);
}
unsigned int getintval(void)
{
  unsigned int v;
  scanf("%d",&v);
  return v;
}
unsigned char getcharval(void)
{
  return (unsigned char)getintval();
}
// End of I/O routines
 
void cfg_init(void)
{
  	if (eeprom_read_byte((void *)0)!=0xA5)
	{
	  // EEPROM has never been set up
	  cfg.sn=1;
	  cfg.delay=10;   
	  cfg.fmt='A';
	}
	else
	{
	   eeprom_read_block(&cfg,(void *)0,sizeof(cfg));
	}
}
struct cfgdata
  {
  char *name;
  void *var;
  unsigned char typ;
  };
void configure(void) 
{
    int n;
    struct cfgdata cdata[]=   // note this is on the stack, so it won't consume memory forever
	  { 
		{"Serial #", &cfg.sn, 0}, 
		{"Delay", &cfg.delay,0},
		{"Format",&cfg.fmt,1},
	  };
    while (1)
	{
		int c;
		putstr("Setup\n"); 
		putstr("A - Exit\n");
		putstr("B - Save\n");
		putstr("C - Diagnostic\n");
// generate dynamic menu (D-Z)
		for (n=0;n<sizeof(cdata)/sizeof(cdata[0]);n++)
		  {
		  putchar(n+'D');
		  putchar('-');
		  putstr(cdata[n].name);
		  putchar(':');
		  if (cdata[n].typ==0) putint(*(unsigned *)cdata[n].var);
		  if (cdata[n].typ==1) putint(*(unsigned char *)cdata[n].var);
		  putchar('\n');
		  }
		c=getchar();  // get menu choice
		c=toupper(c)-'A';
		if (c==0) return;
		if (c==1)
		{
			cfg.mark=0xA5;
			eeprom_write_block(&cfg,(void *)0,sizeof(cfg));
			putstr("OK\n");
			continue;
		}
		if (c==2)
		{
		// Do diagnostic code here
		}
		c-=3;  // convert to cdata index
		if (c<sizeof(cdata)/sizeof(cdata[0])) //don't process out of bounds
		  {
		  putstr(cdata[c].name);
		  putchar(':');
		  if (cdata[c].typ==0) *(unsigned int *)cdata[c].var=getintval();
		  if (cdata[c].typ==1) *(unsigned char *)cdata[c].var=getcharval();
		  }
    }
		
}  

 

About Printing

Many of you like to print the project of the month out for your personal use and that's fine with us. However, when you print, you may find that the page is too wide to print correctly on your printer. This is how the browser handles wide pages and is not something we do deliberately to prevent printing. First, many printers have a "scale to fit page" option. That will usually work pretty well. The other alternative is to print in landscape mode! Usually the wider page will fit the project with no troubles. We do ask that you not post the projects to the Web or otherwise distribute them, but personal copies for your own use are fine!

Here's another tip sent in by Dustin Christopherson:

I sure love your site and your information is very helpful. I noticed on your Basic Stamp Circuit of the Month it has a section about printing the document. Just thought I would let you know that one of the ways I find that works best with printing such a page is to select the text and leave the graphics on the left unselected. Then, when you go to print the document, select the "print selection" option. This way, none of the navigational buttons for the web site are printed and you don't have a big margin on the left and none of the text is cut off. Also, doing it this way will allow you to print portrait. I just printed the Basic Stamp Circuit of the Month using this technique and it looks great!

Thanks Dustin!

Feed Back

Are you reading these projects regularly? Are they helpful? What would you like to see? How about PIC projects some months instead of Stamps? Do you prefer Stamp I or Stamp II projects? Take a moment to share your thoughts on the Stamp Project of the Month.

Back Home

horizontal rule

This article is copyright 1999, 2000, 2001, 2002, 2003 by AWC. All Rights Reserved.

horizontal rule

[Kits] | [Math] | [PS/2] | [Pulse In] | [Pulse Out] | [I/O] | [A/D] | [PWM] | [Position Sensing] [PIC Programming] | [RS232] | [PLD/FPGA] | [NetPorter]
[MicroTasks/Consulting] | [Components] | [Products] | [News] | [Search] | [Documents] | [Projects] | [Resources] | [Updates] | [FAQ] | [Support] [PDF Catalog]
Jump to PAKs: I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII PicoPAK: VIII

[View Cart/Checkout]

Site contents © 1997-2008 by AWC, 310 Ivy Glen, League City, TX 77573    (281) 334-4341