Electronic Sculpture Archive
Bruce Cannon

This site is an archive of my older interactive,  electronic and computer-controlled sculpture.  You can always find out about my latest projects via my gateway, brucecannon.com.

   

treetime.c

code listing for the TreeTime sculpture

Below is the code inside this robotic sculpture.  More details are available on the technical notes page, and this piece's main page.   This code also relies on my standard header file my_prefs.h and my LCD display driver my_lcd.c.  This is code for Custom Computer Services PIC C compiler.

As with all my code listings, you may need to copy them to an editor which supports 4-char tabs to see and print them correctly.

/*
treetime.C
Bruce Cannon
06-13-98
TreeTime is a robotic, interactive sculpture which explores the other side of speed.
It is constructed from pieces of a laurel tree which had been struck by lightning
during this past winter's storms.  It was scavenged from the Carquinez Scenic Shoreline.
Working from this Shelleyan beginning, the tree was reassembled using the best of
both the technological and natural.  Motors, wires and cables reanimate the corpse,
and new agendas are imposed upon the resulting cybernetic organism.
This machine senses the presence of viewers and moves in response but, most often,
this movement is imperceptibly slow.  For the most part, this sculpture will appear to be
static.  Every once in a while, however, a viewer might see a visible movement.
There are six large AC gearmotors at the base of the sculpture, which through cams and
cable assemblies, articulate the limbs of the tree.  There are sixteen small DC motors
which move the small sprigs at the ends of the branches.  There are seven light sensors at
various locations around the piece.  The sculpture constantly polls its sensors for rapid
changes in light levels.  It moves a series of motors in response.  There is a tiny red
light associated with each motor, which blinks when that motor is pulsed.
So a viewer may notice a ripple of light activity around the tree when a shadow is cast
across a sensor.
Cables connect the tree to its computer control system.  These cables contain nearly 100
individual signals required for sculpture control.
The box contains a power supply, a powerful microcontroller, and AC and DC motor driver
subsystems.  Here decisions are made about viewer presence and tree response.  An LCD
panel shows status information, including a binary representation of which sensors are
being triggered and which motors are being moved in response.
*/
/***************************************************************************************
Preprocessor setup.
***************************************************************************************/
//#include <16C74A.H>
#include <d:\electron\sculpts\treetime\16C74A.H>
#fuses XT,NOWDT,NOPROTECT,PUT,BROWNOUT
//#include <my_prefs.H>
#include <d:\electron\sculpts\treetime\my_prefs.H>
#use delay(clock=8000000)
//#include <my_lcd.c>
#include <d:\electron\sculpts\treetime\my_lcd.c>
/****************************************************************************************
Port labels.
****************************************************************************************/
#byte ANALOG = 5
#bit unused_A = analog.4
struct output_control_map
{				//This structure is overlayed onto an I/O port
	byte address : 4;	//to gain access to small groups of pins.
	byte _ports : 3;        //The bits are allocated from low order up.
	bit _CE;
} control;			//This puts the entire structure
#byte control = 7		//on to port C (at address 7).
struct system_io_map
{
	bit no_sensing;		//Neg true front panel switches: Ignore sensors?
	bit no_LEDs;		//			"	 Blinking lights?
	bit no_AC;		//			"	 Move the AC motors?
	bit no_sound;		//ISD is not yet in place, but this will turn sound off.
	bit clock;		//Front panel lamp used for feedback.
	bit ZCD;		//This pin can be tied thru 10k to transformer sec., if
				//it seems necc.  Not used for now.
	bit unused1;
	bit unused2;
} system;
#byte system = 8		//Sets this structure onto port D.
/****************************************************************************************
System data definitions.
****************************************************************************************/
#define NUM_SENSORS 7			//How many CdS cells there are on the tree.
#define NUM_MOTORS 22			//Total number of motors on tree.
					//(there are also this many LEDs).
#define NUM_PORTS ((NUM_MOTORS/8)+1)	//Each port is a 74HC4514, which is a 1-16 mux.
#define NUM_BYTES NUM_PORTS		//Half the pins drive motors, and half LEDs.
					//(Mots even, LEDs odd). 8 motors/port means 1 byte
					//needed for each port to store a pattern of movement.
#define ALL_DISABLED 7			//Value to write to Control to turn off all ports
#define AC_PORT 2			//Which port are the AC motors on? (from zero)
#define AC_MOTORS 16			//Where in the string of motors do the ACs begin?
					//(from zero).
/****************************************************************************************
Switches.
****************************************************************************************/
#define SENSE_RATE 100			//Time between A/D reads in ms.
#define TRIGGER_THRESHOLD 2		//Difference between consec. A/D reads considered movement.
#define SIGNATURE_TIME 10000		//Time to display intro screen, in ms.
#define VIEW_TIME 400			//In ms, time which a motor bit is displayed.
#define CLOCK_BLINK 100			//In ms, duration of LED pulse.
#define TREE_TIME 5000			//Clock blink rate.
#define MOVE_TIME 240			//Number of TreeTimes between actual motor movements.
#define BETWEEN_MOTORS 100		//Rate at which motors are pulsed in random_sequence().
#define LED_PULSE 100			//Duration of LED in ms.
#define AC_PULSE 50			//Duration of AC motor on-time in ms.
#define DC_PULSE 50			//Duration of DC motor on-time in ms.
#define RAMP_RATE 10			//Number of cycles to hold ea duty, at least 1 (0 = never on).
					//Increase to make ramp shallower.
#define RAMP_UNIT 1			//Incr/decr duty by this ea cycle, at least 1 (0 = always on).
					//Increase to make ramp steeper.
#define FULL_SPEED_TIME 10		//Cycles to hold full speed (approx in ms).
#define MIN_CYCLE 1			//Min duty cycle.  Must be at least 1 multiple of RAMP_UNIT
					//or motor will never stop.
#define MAX_CYCLE 30			//Max duty cycle; lowest that still moves under greatest load.
//Screen positions for variables:
#define SCREEN_SENSORS 136
#define SCREEN_SENSE 152
#define SCREEN_AC 159
#define SCREEN_LEDS 167
#define SCREEN_MOTORS 199
#define SCREEN_SOUND 231
#define NUM_SWITCHES 4
//The following order must conform to the port struct above:
byte const screen_positions[NUM_SWITCHES] =
{SCREEN_SENSE,SCREEN_LEDS,SCREEN_AC,SCREEN_SOUND};
//This array holds a sense-response motor movement sequence for each sensor.
//Tune for correct location near each sensor!
byte const motor_patterns[NUM_SENSORS][NUM_PORTS] =	{{0x01,0x00,0x05},
							{0x09,0x00,0x03},
							{0x07,0x00,0x03},
							{0x10,0xC0,0x04},
							{0xE0,0x04,0x08},
							{0x00,0x0B,0x10},
							{0x00,0xB0,0x20}};

/****************************************************************************************
Prototypes.
****************************************************************************************/
void initialize_74();
void setup_screen();
byte check_sensors();
void update_display(byte tripped_sensors);
byte random5();
void rand_mot(byte motor_range);
void pulse_motor(byte which_motor);
void movement_sequence(byte motor_range, byte tripped_sensors);
/****************************************************************************************
ISR, and global variables for it.
RTCC is used as the master clock, so that various routines can happen in a relatively
coordinated fashion.  Accuracy of built-in delays suffers a bit, but nothing in this
program fails if delays are extended.
Global variables are used because I want to initialize them to zero, and am not sure
whether static vars are automatically cleared to zero or not.  Also, want sense response
funct to be able to clear/reset motor move timer each time movement is detected.
****************************************************************************************/
bit move_enable = 0;
bit clock_LED_enable = 0;
word clock_timer = 0;
word move_timer = 0;
#INT_RTCC
Tree_clock()
{
	if(++clock_timer >= TREE_TIME)
	{
		clock_timer = 0;
		clock_LED_enable = 1;
		if(++move_timer >= MOVE_TIME)
		{
			move_timer = 0;
			move_enable = 1;
		}
	}
}
/****************************************************************************************
Sets up the LCD screen, plays the signature, checks switches, checks sensors, moves the
joints in response, updates screen values.
MOTORS: If sensing, motors associated with each triggered sensor are pulsed each period.
	If not, one motor is pulsed ea period.
	AC and DC motors have different pulse durations.
LCD: 	Main updates all sensor and switch values, motor routines update motor values.
****************************************************************************************/
main()
{
	bit motor_LED_enable;
	byte tripped_sensors,sensor,motor_range,last_sensors;
	initialize_74();		//Set up ports and peripherals.
	setup_screen();
	while(TRUE)
	{
		if(!system.no_sound)
		{
			//Here's is where I'll trigger random ambient sound, later.
		}
		if(system.no_AC)			//If AC motors disabled,
		{
			motor_range = AC_MOTORS;	//limit range to less than them.
		}
		else
		{
			motor_range = NUM_MOTORS;	//otherwise pick from all of them.
		}
		if(clock_LED_enable)			//Blink every x main loops.
		{
			system.clock = ON;		//Front panel LED.
			delay_ms(CLOCK_BLINK);
			system.clock = OFF;
			clock_LED_enable = 0;		//Unset ISR flag.
			motor_LED_enable = 1;		//When mots fixed, this could be move_en.
		}					//If no sense, gives mot LED every TREETIME.
		if(system.no_sensing || move_enable || motor_LED_enable) //If sensing disabled, or
		{						//X min without a sense:
			tripped_sensors = 0;			//Clear sensor result for display.
			update_display(tripped_sensors);	//Update switch positions, sensors
								//(motors updated in motor functs).
			if(motor_LED_enable || move_enable)	//Move a random LED every X mains,
			{					//and a motor every Y mains.
				rand_mot(motor_range);		//Pulse a motor and/or LED.
				motor_LED_enable = 0;		//Unset until next TREETIME.
			}
		}
		else						//If sensing,
		{
			tripped_sensors = check_sensors();	//get results of movement detection.
			update_display(tripped_sensors);	//Update switch positions + sensors
								//(motors updated in motor functs).
			if(tripped_sensors > 0)			//NEVER move when people around:
			{
				move_timer = 0;			//Clear ISR timer.
				move_enable = 0;		//Unset ISR flag.
			}
			if(tripped_sensors != last_sensors)	//If different than last read,
			{
				movement_sequence(motor_range,tripped_sensors);   //do sequence,
				last_sensors = tripped_sensors;	//save for next time.
			}
		}
	}
}
/****************************************************************************************
Runs a sequence of motor movements based on sensor inputs.  Takes a byte which represents
the sensors triggered, OR together all the bits from all the selected patterns, then do
that response.
LCD screen updated before running ports; pattern is visible for BETWEEN_MOTORS*NUM_MOTORS.
****************************************************************************************/
void movement_sequence(byte motor_range, byte tripped_sensors)
{
	byte which_sensor,which_port,which_motor,temp;
	byte new_pattern[NUM_PORTS];
	//Clear the result array:
	for(which_port = 0; which_port < NUM_PORTS; which_port++)
	{
		new_pattern[which_port] = 0;
	}
	//Then for each 1 bit in tripped_sensors, OR that array into the result array
	//(The end result is a cumulative pattern for all the stimulated motors):
	for(which_sensor = 0; which_sensor < NUM_SENSORS; which_sensor++)
	{
		if(bit_test(tripped_sensors,which_sensor))
		{
			for(which_port = 0; which_port < NUM_PORTS; which_port++)
			{
				new_pattern[which_port] |= motor_patterns[which_sensor][which_port];
			}
		}
	}
	//Write the resulting pattern to the screen:
	LCD_write(SCREEN_MOTORS);						//Set the LCD display to the motor area.
	for(which_motor = 0; which_motor < NUM_MOTORS; which_motor++)		//Motors are even nos.
	{									//LEDs are odds.
		if(bit_test(new_pattern[which_motor / 8],which_motor % 8))	//If bit set in pattern,
		{
			if(which_motor < motor_range)				//if within enabled range,
			{
				LCD_write('1');					//Update display.
			}
		}
		else
		{
			LCD_write('0');						//	"
		}
	}
	//Run the resulting pattern through the ports:
	for(which_motor = 0; which_motor < motor_range; which_motor++)		//Motors are even nos.
	{									//LEDs are odds.
		if(bit_test(new_pattern[which_motor / 8],which_motor % 8))	//If bit set in pattern,
		{
			pulse_motor(which_motor);
			delay_ms(BETWEEN_MOTORS);				//Rate of LED ripple.
		}
	}
}
/****************************************************************************************
Reads each sensor, and compares the readings to the last ones.  Sets a bit in the result
byte for each change in light levels over TRIGGER_THRESHOLD.  Returns the number.
****************************************************************************************/
byte check_sensors()
{
	byte sensor,difference,result;
	byte this_read[NUM_SENSORS];
	byte last_read[NUM_SENSORS];
	result = 0;						//Clear result buffer.
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)
	{
		set_adc_channel(sensor);			//Change channel.
		delay_US(20);					//Wait required sampling time.
		last_read[sensor] = read_ADC();			//Get sensor values.
	}
	delay_ms(SENSE_RATE);					//wait a while,
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)
	{
		set_adc_channel(sensor);			//Change channel.
		delay_US(20);					//Wait required sampling time.
		this_read[sensor] = read_ADC();			//Get some more sensor values.
	}
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)		//Compare readings:
	{
		if(this_read[sensor] > last_read[sensor])	//Subtract them.
		{
			difference = this_read[sensor] - last_read[sensor];	//	"
		}
		else
		{
			difference = last_read[sensor] - this_read[sensor];	//	"
		}
		if(difference >= TRIGGER_THRESHOLD)		//If they differ by x,
		{
			bit_set(result,sensor);			/Set the bit.
		}
	}
	return(result);
}
/****************************************************************************************
Set up PIC IO registers and hardware peripherals.
****************************************************************************************/
void initialize_74()
{
	//Set up port-extender control lines:
	PORTC = 255;				//All control lines are neg true, so make high to disable.
	set_tris_c(ALL_OUTPUTS);		//All control and address lines are outputs.
	//Set up LCD port:
	PORTB = OFF;				//All positive logic; all low.
	set_tris_b(ALL_OUTPUTS);		//All LCD lines are outs.
	//Set up system-control port:
	setup_PSP(PSP_DISABLED);		//Turn off parallel slave port.
	PORTD = 0;				//All outputs low.
	set_tris_D(0x0F);			//Buttons inputs, all else outputs.
	//Set up A/D:
	setup_port_A(ALL_ANALOG);		//8 A/D inputs with Vdd ref.
	setup_ADC(ADC_CLOCK_INTERNAL);		//Use main clock (8MHz) as source for A/D clock.
   	setup_ADC(ADC_CLOCK_DIV_32);		//Fosc/32 gives +/-4us / bit and 38us ttl conv. time.
	set_tris_a(ALL_INPUTS - 16);		//All sensor inputs ins, unused RA4 pin out.
	set_tris_e(7);				//Low three bits are dir bits for analog pins 5-7.
	//Set up RTCC for TreeTime main counter:
	setup_counters(RTCC_INTERNAL,RTCC_DIV_8);
	enable_interrupts(RTCC_ZERO);
	enable_interrupts(GLOBAL);
}
/****************************************************************************************
Ea time called, pulses one of the motors, using the five-bit PRBG to decide which one.
Also updates display; Pulsed bit is visible VIEW_TIME.  Checks for AC enable as well.
Until I get better motors, also checks for motor_enable.
****************************************************************************************/
void rand_mot(byte motor_range)
{
	byte which_motor,places;
	do
	{
		which_motor = random5();		//Get a random number 0 - 30.
	}
	while(which_motor >= motor_range);		//Until within range.
	LCD_write(SCREEN_MOTORS);			//Set screen start point.
	for(places = 0; places < which_motor; places++)	//Zero up to current motor.
	{
		LCD_write('0');				//	"
	}
	pulse_motor(which_motor);			//Pulse motor.
	LCD_write('1');					//Update screen.
	delay_ms(VIEW_TIME);				//Time to view.
	LCD_write((SCREEN_MOTORS + places));		//Back up a step.
	//Clear rest of screen places:
	for(places = which_motor; places < NUM_MOTORS; places++)
	{
		LCD_write('0');
	}
}
/****************************************************************************************
Clocks a static five-bit PRBG; subtracts one since PRBG never zero, returns result which
will be 0 - 30.
To create PRBGs of other maximal lengths, BIT_MASK, M and N must be changed together
(see Art of Electronics).
****************************************************************************************/
byte random5()
{
	#define BIT_MASK 31			//This is a five-bit register.
	static byte reg;			//The shift register.
	#bit M = reg.4				//length of SR, output and one tap.
	#bit N = reg.2				//Other tap.
	#bit FEED = reg.0			//feedback point.
	bit newbit;				//The XOR workspace.
	if(reg == 0)				//It won't work if it's empty.
	{
		reg = 0xFF;
	}
	else
	{
		newbit = ((M && !N) || (!M && N));	//XOR the taps together.
		reg <<= 1;				//Shift register left.
		FEED = newbit;				//Put in value.
	}
	return((reg & BIT_MASK) - 1);			//Strip away higher bits, and include zero.
}
/****************************************************************************************
Pulses the given motor, then pulses its corresponding LED.
Checks AC enable and LED enable, skipping if disabled.
****************************************************************************************/
void pulse_motor(byte which_motor)
{
	byte mask,motor_time;
	control.address = (which_motor % 8) * 2; //Select 1 of 8 motors in a 16 pin port.
						//Motors are on even pins, LEDs are odd.
	mask = 1;				//Preload port-select workspace.
	mask <<= (which_motor / 8);		//Shift over to desired port bit.
	mask ^= 7;				//Invert: port enable lines are neg true.
	//Remove when I get different motors:
	if(move_enable)
	{
		if(which_motor >= AC_MOTORS)	//If AC motor,
		{
			if(!system.no_AC)	//and AC enabled,
			{
				motor_time = AC_PULSE;	//Set AC time.
			}
		}
		else
		{
			motor_time = DC_PULSE;		//Otherwise use DC time.
		}

 

treetime.c

code listing for the TreeTime sculpture

Below is the code inside this robotic sculpture.  More details are available on the technical notes page, and this piece's main page.   This code also relies on my standard header file my_prefs.h and my LCD display driver my_lcd.c.  This is code for Custom Computer Services PIC C compiler.

As with all my code listings, you may need to copy them to an editor which supports 4-char tabs to see and print them correctly.

/*
treetime.C
Bruce Cannon
06-13-98
TreeTime is a robotic, interactive sculpture which explores the other side of speed.
It is constructed from pieces of a laurel tree which had been struck by lightning
during this past winter's storms.  It was scavenged from the Carquinez Scenic Shoreline.
Working from this Shelleyan beginning, the tree was reassembled using the best of
both the technological and natural.  Motors, wires and cables reanimate the corpse,
and new agendas are imposed upon the resulting cybernetic organism.
This machine senses the presence of viewers and moves in response but, most often,
this movement is imperceptibly slow.  For the most part, this sculpture will appear to be
static.  Every once in a while, however, a viewer might see a visible movement.
There are six large AC gearmotors at the base of the sculpture, which through cams and
cable assemblies, articulate the limbs of the tree.  There are sixteen small DC motors
which move the small sprigs at the ends of the branches.  There are seven light sensors at
various locations around the piece.  The sculpture constantly polls its sensors for rapid
changes in light levels.  It moves a series of motors in response.  There is a tiny red
light associated with each motor, which blinks when that motor is pulsed.
So a viewer may notice a ripple of light activity around the tree when a shadow is cast
across a sensor.
Cables connect the tree to its computer control system.  These cables contain nearly 100
individual signals required for sculpture control.
The box contains a power supply, a powerful microcontroller, and AC and DC motor driver
subsystems.  Here decisions are made about viewer presence and tree response.  An LCD
panel shows status information, including a binary representation of which sensors are
being triggered and which motors are being moved in response.
*/
/***************************************************************************************
Preprocessor setup.
***************************************************************************************/
//#include <16C74A.H>
#include <d:\electron\sculpts\treetime\16C74A.H>
#fuses XT,NOWDT,NOPROTECT,PUT,BROWNOUT
//#include <my_prefs.H>
#include <d:\electron\sculpts\treetime\my_prefs.H>
#use delay(clock=8000000)
//#include <my_lcd.c>
#include <d:\electron\sculpts\treetime\my_lcd.c>
/****************************************************************************************
Port labels.
****************************************************************************************/
#byte ANALOG = 5
#bit unused_A = analog.4
struct output_control_map
{				//This structure is overlayed onto an I/O port
	byte address : 4;	//to gain access to small groups of pins.
	byte _ports : 3;        //The bits are allocated from low order up.
	bit _CE;
} control;			//This puts the entire structure
#byte control = 7		//on to port C (at address 7).
struct system_io_map
{
	bit no_sensing;		//Neg true front panel switches: Ignore sensors?
	bit no_LEDs;		//			"	 Blinking lights?
	bit no_AC;		//			"	 Move the AC motors?
	bit no_sound;		//ISD is not yet in place, but this will turn sound off.
	bit clock;		//Front panel lamp used for feedback.
	bit ZCD;		//This pin can be tied thru 10k to transformer sec., if
				//it seems necc.  Not used for now.
	bit unused1;
	bit unused2;
} system;
#byte system = 8		//Sets this structure onto port D.
/****************************************************************************************
System data definitions.
****************************************************************************************/
#define NUM_SENSORS 7			//How many CdS cells there are on the tree.
#define NUM_MOTORS 22			//Total number of motors on tree.
					//(there are also this many LEDs).
#define NUM_PORTS ((NUM_MOTORS/8)+1)	//Each port is a 74HC4514, which is a 1-16 mux.
#define NUM_BYTES NUM_PORTS		//Half the pins drive motors, and half LEDs.
					//(Mots even, LEDs odd). 8 motors/port means 1 byte
					//needed for each port to store a pattern of movement.
#define ALL_DISABLED 7			//Value to write to Control to turn off all ports
#define AC_PORT 2			//Which port are the AC motors on? (from zero)
#define AC_MOTORS 16			//Where in the string of motors do the ACs begin?
					//(from zero).
/****************************************************************************************
Switches.
****************************************************************************************/
#define SENSE_RATE 100			//Time between A/D reads in ms.
#define TRIGGER_THRESHOLD 2		//Difference between consec. A/D reads considered movement.
#define SIGNATURE_TIME 10000		//Time to display intro screen, in ms.
#define VIEW_TIME 400			//In ms, time which a motor bit is displayed.
#define CLOCK_BLINK 100			//In ms, duration of LED pulse.
#define TREE_TIME 5000			//Clock blink rate.
#define MOVE_TIME 240			//Number of TreeTimes between actual motor movements.
#define BETWEEN_MOTORS 100		//Rate at which motors are pulsed in random_sequence().
#define LED_PULSE 100			//Duration of LED in ms.
#define AC_PULSE 50			//Duration of AC motor on-time in ms.
#define DC_PULSE 50			//Duration of DC motor on-time in ms.
#define RAMP_RATE 10			//Number of cycles to hold ea duty, at least 1 (0 = never on).
					//Increase to make ramp shallower.
#define RAMP_UNIT 1			//Incr/decr duty by this ea cycle, at least 1 (0 = always on).
					//Increase to make ramp steeper.
#define FULL_SPEED_TIME 10		//Cycles to hold full speed (approx in ms).
#define MIN_CYCLE 1			//Min duty cycle.  Must be at least 1 multiple of RAMP_UNIT
					//or motor will never stop.
#define MAX_CYCLE 30			//Max duty cycle; lowest that still moves under greatest load.
//Screen positions for variables:
#define SCREEN_SENSORS 136
#define SCREEN_SENSE 152
#define SCREEN_AC 159
#define SCREEN_LEDS 167
#define SCREEN_MOTORS 199
#define SCREEN_SOUND 231
#define NUM_SWITCHES 4
//The following order must conform to the port struct above:
byte const screen_positions[NUM_SWITCHES] =
{SCREEN_SENSE,SCREEN_LEDS,SCREEN_AC,SCREEN_SOUND};
//This array holds a sense-response motor movement sequence for each sensor.
//Tune for correct location near each sensor!
byte const motor_patterns[NUM_SENSORS][NUM_PORTS] =	{{0x01,0x00,0x05},
							{0x09,0x00,0x03},
							{0x07,0x00,0x03},
							{0x10,0xC0,0x04},
							{0xE0,0x04,0x08},
							{0x00,0x0B,0x10},
							{0x00,0xB0,0x20}};

/****************************************************************************************
Prototypes.
****************************************************************************************/
void initialize_74();
void setup_screen();
byte check_sensors();
void update_display(byte tripped_sensors);
byte random5();
void rand_mot(byte motor_range);
void pulse_motor(byte which_motor);
void movement_sequence(byte motor_range, byte tripped_sensors);
/****************************************************************************************
ISR, and global variables for it.
RTCC is used as the master clock, so that various routines can happen in a relatively
coordinated fashion.  Accuracy of built-in delays suffers a bit, but nothing in this
program fails if delays are extended.
Global variables are used because I want to initialize them to zero, and am not sure
whether static vars are automatically cleared to zero or not.  Also, want sense response
funct to be able to clear/reset motor move timer each time movement is detected.
****************************************************************************************/
bit move_enable = 0;
bit clock_LED_enable = 0;
word clock_timer = 0;
word move_timer = 0;
#INT_RTCC
Tree_clock()
{
	if(++clock_timer >= TREE_TIME)
	{
		clock_timer = 0;
		clock_LED_enable = 1;
		if(++move_timer >= MOVE_TIME)
		{
			move_timer = 0;
			move_enable = 1;
		}
	}
}
/****************************************************************************************
Sets up the LCD screen, plays the signature, checks switches, checks sensors, moves the
joints in response, updates screen values.
MOTORS: If sensing, motors associated with each triggered sensor are pulsed each period.
	If not, one motor is pulsed ea period.
	AC and DC motors have different pulse durations.
LCD: 	Main updates all sensor and switch values, motor routines update motor values.
****************************************************************************************/
main()
{
	bit motor_LED_enable;
	byte tripped_sensors,sensor,motor_range,last_sensors;
	initialize_74();		//Set up ports and peripherals.
	setup_screen();
	while(TRUE)
	{
		if(!system.no_sound)
		{
			//Here's is where I'll trigger random ambient sound, later.
		}
		if(system.no_AC)			//If AC motors disabled,
		{
			motor_range = AC_MOTORS;	//limit range to less than them.
		}
		else
		{
			motor_range = NUM_MOTORS;	//otherwise pick from all of them.
		}
		if(clock_LED_enable)			//Blink every x main loops.
		{
			system.clock = ON;		//Front panel LED.
			delay_ms(CLOCK_BLINK);
			system.clock = OFF;
			clock_LED_enable = 0;		//Unset ISR flag.
			motor_LED_enable = 1;		//When mots fixed, this could be move_en.
		}					//If no sense, gives mot LED every TREETIME.
		if(system.no_sensing || move_enable || motor_LED_enable) //If sensing disabled, or
		{						//X min without a sense:
			tripped_sensors = 0;			//Clear sensor result for display.
			update_display(tripped_sensors);	//Update switch positions, sensors
								//(motors updated in motor functs).
			if(motor_LED_enable || move_enable)	//Move a random LED every X mains,
			{					//and a motor every Y mains.
				rand_mot(motor_range);		//Pulse a motor and/or LED.
				motor_LED_enable = 0;		//Unset until next TREETIME.
			}
		}
		else						//If sensing,
		{
			tripped_sensors = check_sensors();	//get results of movement detection.
			update_display(tripped_sensors);	//Update switch positions + sensors
								//(motors updated in motor functs).
			if(tripped_sensors > 0)			//NEVER move when people around:
			{
				move_timer = 0;			//Clear ISR timer.
				move_enable = 0;		//Unset ISR flag.
			}
			if(tripped_sensors != last_sensors)	//If different than last read,
			{
				movement_sequence(motor_range,tripped_sensors);   //do sequence,
				last_sensors = tripped_sensors;	//save for next time.
			}
		}
	}
}
/****************************************************************************************
Runs a sequence of motor movements based on sensor inputs.  Takes a byte which represents
the sensors triggered, OR together all the bits from all the selected patterns, then do
that response.
LCD screen updated before running ports; pattern is visible for BETWEEN_MOTORS*NUM_MOTORS.
****************************************************************************************/
void movement_sequence(byte motor_range, byte tripped_sensors)
{
	byte which_sensor,which_port,which_motor,temp;
	byte new_pattern[NUM_PORTS];
	//Clear the result array:
	for(which_port = 0; which_port < NUM_PORTS; which_port++)
	{
		new_pattern[which_port] = 0;
	}
	//Then for each 1 bit in tripped_sensors, OR that array into the result array
	//(The end result is a cumulative pattern for all the stimulated motors):
	for(which_sensor = 0; which_sensor < NUM_SENSORS; which_sensor++)
	{
		if(bit_test(tripped_sensors,which_sensor))
		{
			for(which_port = 0; which_port < NUM_PORTS; which_port++)
			{
				new_pattern[which_port] |= motor_patterns[which_sensor][which_port];
			}
		}
	}
	//Write the resulting pattern to the screen:
	LCD_write(SCREEN_MOTORS);						//Set the LCD display to the motor area.
	for(which_motor = 0; which_motor < NUM_MOTORS; which_motor++)		//Motors are even nos.
	{									//LEDs are odds.
		if(bit_test(new_pattern[which_motor / 8],which_motor % 8))	//If bit set in pattern,
		{
			if(which_motor < motor_range)				//if within enabled range,
			{
				LCD_write('1');					//Update display.
			}
		}
		else
		{
			LCD_write('0');						//	"
		}
	}
	//Run the resulting pattern through the ports:
	for(which_motor = 0; which_motor < motor_range; which_motor++)		//Motors are even nos.
	{									//LEDs are odds.
		if(bit_test(new_pattern[which_motor / 8],which_motor % 8))	//If bit set in pattern,
		{
			pulse_motor(which_motor);
			delay_ms(BETWEEN_MOTORS);				//Rate of LED ripple.
		}
	}
}
/****************************************************************************************
Reads each sensor, and compares the readings to the last ones.  Sets a bit in the result
byte for each change in light levels over TRIGGER_THRESHOLD.  Returns the number.
****************************************************************************************/
byte check_sensors()
{
	byte sensor,difference,result;
	byte this_read[NUM_SENSORS];
	byte last_read[NUM_SENSORS];
	result = 0;						//Clear result buffer.
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)
	{
		set_adc_channel(sensor);			//Change channel.
		delay_US(20);					//Wait required sampling time.
		last_read[sensor] = read_ADC();			//Get sensor values.
	}
	delay_ms(SENSE_RATE);					//wait a while,
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)
	{
		set_adc_channel(sensor);			//Change channel.
		delay_US(20);					//Wait required sampling time.
		this_read[sensor] = read_ADC();			//Get some more sensor values.
	}
	for(sensor = 0; sensor < NUM_SENSORS; sensor++)		//Compare readings:
	{
		if(this_read[sensor] > last_read[sensor])	//Subtract them.
		{
			difference = this_read[sensor] - last_read[sensor];	//	"
		}
		else
		{
			difference = last_read[sensor] - this_read[sensor];	//	"
		}
		if(difference >= TRIGGER_THRESHOLD)		//If they differ by x,
		{
			bit_set(result,sensor);			/Set the bit.
		}
	}
	return(result);
}
/****************************************************************************************
Set up PIC IO registers and hardware peripherals.
****************************************************************************************/
void initialize_74()
{
	//Set up port-extender control lines:
	PORTC = 255;				//All control lines are neg true, so make high to disable.
	set_tris_c(ALL_OUTPUTS);		//All control and address lines are outputs.
	//Set up LCD port:
	PORTB = OFF;				//All positive logic; all low.
	set_tris_b(ALL_OUTPUTS);		//All LCD lines are outs.
	//Set up system-control port:
	setup_PSP(PSP_DISABLED);		//Turn off parallel slave port.
	PORTD = 0;				//All outputs low.
	set_tris_D(0x0F);			//Buttons inputs, all else outputs.
	//Set up A/D:
	setup_port_A(ALL_ANALOG);		//8 A/D inputs with Vdd ref.
	setup_ADC(ADC_CLOCK_INTERNAL);		//Use main clock (8MHz) as source for A/D clock.
   	setup_ADC(ADC_CLOCK_DIV_32);		//Fosc/32 gives +/-4us / bit and 38us ttl conv. time.
	set_tris_a(ALL_INPUTS - 16);		//All sensor inputs ins, unused RA4 pin out.
	set_tris_e(7);				//Low three bits are dir bits for analog pins 5-7.
	//Set up RTCC for TreeTime main counter:
	setup_counters(RTCC_INTERNAL,RTCC_DIV_8);
	enable_interrupts(RTCC_ZERO);
	enable_interrupts(GLOBAL);
}
/****************************************************************************************
Ea time called, pulses one of the motors, using the five-bit PRBG to decide which one.
Also updates display; Pulsed bit is visible VIEW_TIME.  Checks for AC enable as well.
Until I get better motors, also checks for motor_enable.
****************************************************************************************/
void rand_mot(byte motor_range)
{
	byte which_motor,places;
	do
	{
		which_motor = random5();		//Get a random number 0 - 30.
	}
	while(which_motor >= motor_range);		//Until within range.
	LCD_write(SCREEN_MOTORS);			//Set screen start point.
	for(places = 0; places < which_motor; places++)	//Zero up to current motor.
	{
		LCD_write('0');				//	"
	}
	pulse_motor(which_motor);			//Pulse motor.
	LCD_write('1');					//Update screen.
	delay_ms(VIEW_TIME);				//Time to view.
	LCD_write((SCREEN_MOTORS + places));		//Back up a step.
	//Clear rest of screen places:
	for(places = which_motor; places < NUM_MOTORS; places++)
	{
		LCD_write('0');
	}
}
/****************************************************************************************
Clocks a static five-bit PRBG; subtracts one since PRBG never zero, returns result which
will be 0 - 30.
To create PRBGs of other maximal lengths, BIT_MASK, M and N must be changed together
(see Art of Electronics).
****************************************************************************************/
byte random5()
{
	#define BIT_MASK 31			//This is a five-bit register.
	static byte reg;			//The shift register.
	#bit M = reg.4				//length of SR, output and one tap.
	#bit N = reg.2				//Other tap.
	#bit FEED = reg.0			//feedback point.
	bit newbit;				//The XOR workspace.
	if(reg == 0)				//It won't work if it's empty.
	{
		reg = 0xFF;
	}
	else
	{
		newbit = ((M && !N) || (!M && N));	//XOR the taps together.
		reg <<= 1;				//Shift register left.
		FEED = newbit;				//Put in value.
	}
	return((reg & BIT_MASK) - 1);			//Strip away higher bits, and include zero.
}
/****************************************************************************************
Pulses the given motor, then pulses its corresponding LED.
Checks AC enable and LED enable, skipping if disabled.
****************************************************************************************/
void pulse_motor(byte which_motor)
{
	byte mask,motor_time;
	control.address = (which_motor % 8) * 2; //Select 1 of 8 motors in a 16 pin port.
						//Motors are on even pins, LEDs are odd.
	mask = 1;				//Preload port-select workspace.
	mask <<= (which_motor / 8);		//Shift over to desired port bit.
	mask ^= 7;				//Invert: port enable lines are neg true.
	//Remove when I get different motors:
	if(move_enable)
	{
		if(which_motor >= AC_MOTORS)	//If AC motor,
		{
			if(!system.no_AC)	//and AC enabled,
			{
				motor_time = AC_PULSE;	//Set AC time.
			}
		}
		else
		{
			motor_time = DC_PULSE;		//Otherwise use DC time.
		}
		control._ports = mask;			//Turn on (neg. true) port.
		delay_ms(motor_time);			//Wait motor time.
		control._ports = ALL_DISABLED;		//Turn off port.
		move_enable = 0;			//Unset ISR flag.
	}
	if(!system.no_LEDs)				//If LEDs are on,
	{
		control.address += 1;			//Move over to LED bit.
		control._ports = mask;			//Turn on port.
		delay_ms(LED_PULSE);			//Wait LED time.
		control._ports = ALL_DISABLED;		//Turn off port.
	}
}
/****************************************************************************************
Init's LCD display, turns on backlight, plays our names and the date, then puts up the
working screen and returns.
****************************************************************************************/
void setup_screen()
{
	LCD_init();
	LCD_write("           TreeTime 1997-1998           ");
	LCD_write(LINE_TWO);
	LCD_write("   by Bruce Cannon -- with Paul Stout   ");
	delay_ms(SIGNATURE_TIME);
	LCD_write(CLEAR);
	delay_ms(1000);
	LCD_write("SENSORS:          SENSE:    AC:   LEDS: ");
	LCD_write(LINE_TWO);
	LCD_write("MOTORS:                          SOUND: ");
}
/****************************************************************************************
Checks front-panel switches and updates screen; updates sensor info; motor info display
is controlled by motor functions.
****************************************************************************************/
void update_display(byte tripped_sensors)
{
	byte which;
	//Write sensor values:
	LCD_write(SCREEN_SENSORS);			//Go to sensor-result screen pos.
	for(which = 0; which < NUM_SENSORS; which++)
	{
		if(bit_test(tripped_sensors,which))	//For each sensor,
		{
			LCD_write('1');			//write its value.
		}
		else
		{
			LCD_write('0');			//	"
		}
	}
	//Write switch values:
	for(which = 0; which < NUM_SWITCHES; which++)
	{
		LCD_write(screen_positions[which]);	//Go to switch's screen pos.
		if(!bit_test(PORTD,which))		//If enabled,
		{
			LCD_write('1');			//say so.
		}
		else					//If not,
		{
			LCD_write('0');			//say not.
		}
	}
}

 

Site contents licensed under a
Creative Commons License.

Creative Commons License