Below is the file 'firm/master/main.c' from this revision. You can also download the file.

// ### BOILERPLATE ###
// 8^2 Automaton Firmware
// Copyright (C) 2005 Peter Todd <pete@petertodd.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// ### BOILERPLATE ###

#include <pic18fregs.h>
#include <stdint.h>
#include <signal2.h>
#include <stdio.h>
#include <stdlib.h>

#include <../common.h>

#pragma stack 0x200 255

#define FOSC (20000000)

code char at __CONFIG1H config1h = 0xFF & _OSC_HS_1H & _OSCS_OFF_1H;
code char at __CONFIG2L config2l = 0xFF & _PUT_ON_2L & _BODEN_ON_2L & _BODENV_4_2V_2L;
code char at __CONFIG2H config2h = 0xFF & _WDTPS_1_128_2H; // watch-dog timer on, max post-scaler
code char at __CONFIG3H config3H = 0xFF;
code char at __CONFIG4L config4l = 0xFF & _LVP_OFF_4L & _BACKBUG_OFF_4L;
code char at __CONFIG5H config5h = 0xFF;
code char at __CONFIG5L config5l = 0xFF;
code char at __CONFIG6H config6h = 0xFF;
code char at __CONFIG6L config6l = 0xFF;
code char at __CONFIG7H config7h = 0xFF;
code char at __CONFIG7L config7l = 0xFF;

// Dip switch stuff.

// This is the port to read to get the dip switch value from.
#define DIP_SW_PORT (PORTC)

// We define masks and modes. Apply the mask to the port with & and then test for equality to the mode.

// xx1 Reflash the slaves. Tested seperately as this can be done in conjunction with all of the other modes.
#define DIP_SW_REFLASH_MASK (0x01)
#define DIP_SW_REFLASH_MODE (0x01)

// 00x Standard simulation mode. Does the simulation as intended.
#define DIP_SW_NORMAL_MASK (0x06)
#define DIP_SW_NORMAL_MODE (0x00)

// 01x Test mode gradient. Sets each slave's value to it's ID * 4
#define DIP_SW_TEST_GRAD_MASK (0x06)
#define DIP_SW_TEST_GRAD_MODE (0x02)

// 10x Test mode... undefined as of yet
#define DIP_SW_TEST_UNDEF_MASK (0x06)
#define DIP_SW_TEST_UNDEF_MODE (0x04)

// 11x Test mode gang. All slaves are controlled in unison by the DIP switches.
#define DIP_SW_TEST_GANG_MASK (0x06)
#define DIP_SW_TEST_GANG_MODE (0x06)

// Variables

// This is the big one. The two arrays of the status of each cell. There is a
// old and new and we switch between them on each cycle. The +2 is for padding
// so we don't have to deal with any special cases.
uint8_t cell_array[2][grid_xsize + 2][grid_ysize + 2];

#define cell_user_move 255
#define cell_alive 255
#define cell_dead 0

// Vars so we can efficiently switch between old and new.
#define cell_old (cell_old_var)
#define cell_new (cell_new_var)
#define cell_switch() cell_tmp_var = cell_new_var; \
			cell_new_var = cell_old_var; \
			cell_old_var = cell_tmp_var;
uint8_t cell_old_var,cell_new_var,cell_tmp_var;


// This is used to communicate between TMR0 and the main_loop code. Main loop
// sets it, TMR0 clears it.
volatile uint8_t tmr0_wait_sync;
#define tmr0_wait_sync_set (0xFF)
#define tmr0_wait_sync_clear (0x00)

// The current seed for the LFSR routine. We don't bother initalizing this one,
// so long as it's not zero, unlikely, we're fine.
uint8_t rand_seed;

// Returns a random number after permutating the LFSR seed.
uint8_t lfsr_rand(){

	__asm
		BCF	STATUS,0
		RRCF	_rand_seed,W
		BTFSC	STATUS,0
		XORLW	0xB4
		MOVWF	_rand_seed
	_endasm;

	return	rand_seed;
}

#include <serial.c>
#include <i2c.c>
#include <slave_com.c>
#include <bootloader.c>

// Interupts, AKA signals. We have two types that we use, the TMR0 int, and the USART ints.
DEF_INTHIGH(high_int)

DEF_HANDLER(SIG_TMR0,_tmr0_handler)

END_DEF

SIGHANDLER(_tmr0_handler)

{

	// clear the TRM0 synchronization variable for the main loop
	tmr0_wait_sync = tmr0_wait_sync_clear;


	// get some hardware randomness. A note about this... Right now the ADC
	// is connected to a voltage divider consisting of two back to back
	// 4.7k resistors. This connection is jumpered, so the ADC can be left
	// free floating as well. If the ADC is connected to ground or 5V the
	// result is very clean, with maybe the occational least significant
	// byte flip. But through the divider or floating it's incredibly
	// noisy, even more so floating. It's hard to say what patterns are in
	// that noise, but at least some of it must be random. A rather odd
	// result though, not sure why exactly that happens.

	// first set ADC into right justified mode, we want the lower bits as they are most susceptable to random effects
	ADCON1bits.ADFM = 1;

	// select AN4
	ADCON0bits.CHS2 = 1;
	ADCON0bits.CHS1 = 0;
	ADCON0bits.CHS0 = 0;

	// start conversion
	ADCON0bits.GO = 1;

	while (ADCON0bits.GO);

	// done, xor result with rand_seed
	rand_seed ^= ADRESL;
	rand(); // mix it up


	// reenable ourselves
	INTCONbits.T0IF = 0;
}

uint8_t motor_inhibit[8];
void main(){
	static uint8_t x,y,z,i,f;
	static uint8_t dip_sw_status;
	static uint16_t n;
	static char junk[32];

	// Initialization

	// Variables

	// Clear the cell array
	for (i = 0; i < 2; i++){
		for (x = 0; x < grid_xsize + 2; x++){
			for (y = 0; y < grid_ysize + 2; y++){
				cell_array[i][x][y] = 0;
			}
		}
	}

	// Setup old and new
	cell_old_var = 0;
	cell_new_var = 1;

	// Ports
	TRISA = 0;
	TRISB = 0;
	PORTB = 0;
	TRISC = 0;

	// setup dip switches
	TRISCbits.TRISC0 = 1;
	TRISCbits.TRISC1 = 1;
	TRISCbits.TRISC2 = 1;

	// setup i2c
	TRISCbits.TRISC3 = 1;
	TRISCbits.TRISC4 = 1;
	SSPCON1 = 0x28;
	SSPSTAT = 80;
	SSPADD = 0xFF;

#if 0
	// setup TMR0 for interrupts
	// 20,000,000/4=5,000,000/256/256=76ints/sec
	T0CONbits.T0PS0 = 0;
	T0CONbits.T0PS1 = 0;
	T0CONbits.T0PS2 = 0;
	T0CONbits.PSA = 0;
	T0CONbits.T0SE = 0;
	T0CONbits.T0CS = 0; // internal clock source
	T0CONbits.T08BIT = 1; // 8-bit mode
	INTCONbits.T0IF = 0;
	INTCONbits.T0IE = 1;
	INTCONbits.GIE = 1;
	T0CONbits.TMR0ON = 1; // GO!
#endif


	// setup serial
	open_serial();

	// Tell the world we're alive
	rs232_sendbuf("Cell Web Controller $Id: main.c,v 1.20 2005/12/14 00:00:03 pete Exp $\n",255);

	// The power supply on this whole sheebang is a bit touchy, so pause
	// awhile to make sure it's stable.
	rs232_sendbuf("Waiting for power to become stable",255);
	for (i = 1; i < 3; i++){
		sprintf(junk,"...%d",i);
		rs232_sendbuf(junk,sizeof(junk));

		for (x = 0; x < 255; x++){
			for (y = 0; y < 255; y++){
				for (z = 0; z < 10; z++);
				ClrWdt();
			}
		}

		// At end of first wait, turn on FET to provide power to the cells.
		PORTB = 0xFF;
	}
	rs232_sendbuf("...done\n",255);

	// After this we go to various operating modes based on what the dip
	// switches are set too.

	// Save current status for comparison later.
	dip_sw_status = DIP_SW_PORT;

	// Every mode has the option to reflash the slaves first.
	if ((dip_sw_status & DIP_SW_REFLASH_MASK) == DIP_SW_REFLASH_MODE){
		rs232_sendbuf("Reflashing slaves:\m",255);

		for (x = 0;x < grid_xsize; x++){
			for (y = 0; y < grid_ysize; y++){
				f = slave_reflash(x,y,10);

				sprintf(junk,"%d,%d returned %d\n",(unsigned int)x,(unsigned int)y,(unsigned int)f);
				rs232_sendbuf(junk,sizeof(junk));
				ClrWdt();
			}
		}

		rs232_sendbuf("Reflashing done.\n",255);
	}
	else{
		rs232_sendbuf("NOT reflashing slaves.\n",255);
	}


	// All modes require the slaves to be running, so start them up.
	rs232_sendbuf("Sending run command to all slaves:\n",255);

	for (x = 0;x < grid_xsize; x++){
		for (y = 0; y < grid_ysize; y++){
			// try to get the attention of the slave, with timeout
			sprintf(junk,"%d,%d try",x,y);
			rs232_sendbuf(junk,sizeof(junk));
			for (i = 1; i < 11; i++){
				ClrWdt();
				sprintf(junk,"...%d",i);
				rs232_sendbuf(junk,sizeof(junk));
				if (slave_bootload_ping(x,y) == bootload_status_clear){
					// success!
					rs232_sendbuf("...success!\n",255);
					slave_bootload_run(x,y);
					goto slave_run_loop_done;
				}
			}

			rs232_sendbuf("...failed\n",255);

			slave_run_loop_done:
			slave_bootload_run(x,y);
		}
	}

	// Test for the different operating modes.

	// Ganged test mode
	if ((dip_sw_status & DIP_SW_TEST_GANG_MASK) == DIP_SW_TEST_GANG_MODE){
		while(1){
			ClrWdt();
			rs232_sendbuf("Test mode gang, write loop starting\n",255);
			for (x = 0;x < grid_xsize; x++){
				for (y = 0; y < grid_ysize; y++){
					i = ((PORTC & 0x0F) << 4) | (PORTC & 0x0F);
					slave_cell_write(x,y,i);
					sprintf(junk,"value %d,%d = %d\n",(unsigned int)x,(unsigned int)y,(unsigned int)i);
					rs232_sendbuf(junk,31);
				}
			}
		}
	}

	// Gradient test mode
	if ((dip_sw_status & DIP_SW_TEST_GRAD_MASK) == DIP_SW_TEST_GRAD_MODE){
		while(1){
			ClrWdt();
			rs232_sendbuf("Test mode gradient, write loop starting\n",255);
			for (x = 0;x < grid_xsize; x++){
				for (y = 0; y < grid_ysize; y++){
					// The + 3 is so that 7,7 gets a value of 255 rather than 252, do the math.
					i = (((x * grid_xsize) + y) * 4) + 3;
					slave_cell_write(x,y,i);
					sprintf(junk,"value %d,%d = %d\n",(unsigned int)x,(unsigned int)y,(unsigned int)i);
					rs232_sendbuf(junk,31);
				}
			}
		}
	}

	// Normal mode, currently implementing a simple conways game of life system, rules 23
	if ((dip_sw_status & DIP_SW_NORMAL_MASK) == DIP_SW_NORMAL_MODE){
		// The following is this devices uuid...
		cell_array[cell_new][1][1] = 255;
		cell_array[cell_new][1][5] = 255;
		cell_array[cell_new][1][7] = 255;
		cell_array[cell_new][1][8] = 255;
		cell_array[cell_new][2][2] = 255;
		cell_array[cell_new][2][5] = 255;
		cell_array[cell_new][2][7] = 255;
		cell_array[cell_new][3][1] = 255;
		cell_array[cell_new][3][3] = 255;
		cell_array[cell_new][3][4] = 255;
		cell_array[cell_new][3][5] = 255;
		cell_array[cell_new][3][6] = 255;
		cell_array[cell_new][3][8] = 255;
		cell_array[cell_new][4][1] = 255;
		cell_array[cell_new][4][3] = 255;
		cell_array[cell_new][5][3] = 255;
		cell_array[cell_new][5][5] = 255;
		cell_array[cell_new][5][6] = 255;
		cell_array[cell_new][5][7] = 255;
		cell_array[cell_new][6][7] = 255;
		cell_array[cell_new][6][8] = 255;
		cell_array[cell_new][7][2] = 255;
		cell_array[cell_new][7][5] = 255;
		cell_array[cell_new][7][6] = 255;
		cell_array[cell_new][7][7] = 255;
		cell_array[cell_new][8][2] = 255;
		cell_array[cell_new][8][3] = 255;
		cell_array[cell_new][8][4] = 255;
		cell_array[cell_new][8][5] = 255;
		cell_array[cell_new][8][7] = 255;
		cell_array[cell_new][8][8] = 255;

		while (1){
			ClrWdt();

			cell_switch();


			// calculate the new state for each cell
			for (x = 1; x < (grid_xsize + 1); x++){
				for (y = 1; y < (grid_ysize + 1); y++){
					// Add up the values of the adjacent cells.
					n =
						(uint16_t)cell_array[cell_old][x - 1][y - 1] +
						(uint16_t)cell_array[cell_old][x    ][y - 1] +
						(uint16_t)cell_array[cell_old][x + 1][y - 1] +
						(uint16_t)cell_array[cell_old][x - 1][y    ] +
											// middle cell not used, yet
						(uint16_t)cell_array[cell_old][x + 1][y    ] +
						(uint16_t)cell_array[cell_old][x - 1][y + 1] +
						(uint16_t)cell_array[cell_old][x    ][y + 1] +
						(uint16_t)cell_array[cell_old][x + 1][y + 1];

					// Calculate the new state for the cell based on the rules 23/3

					// Cell going to be born? Also handles the case of a existing cell surviving.
					if (n == (255 * 3)){
						cell_array[cell_new][x][y] = 255;
					}
					// Cell going to survive? (or stay dead)
					else if (n == (255 * 2)){
						cell_array[cell_new][x][y] = cell_array[cell_old][x][y];
					}
					// Kill the cell.
					else{
						cell_array[cell_new][x][y] = 0;
					}
				}
			}

			// calculated next state of simulation, update slaves
			for (x = 1; x < (grid_xsize + 1); x++){
				for (y = 1; y < (grid_ysize + 1); y++){
					// tell slave what it's new state should be
					if (cell_array[cell_new][x][y] == cell_dead)
						f = 0;
					if (cell_array[cell_new][x][y] >= cell_alive)
						f = 255;
					slave_cell_write(x - 1,y - 1,f);
				}
			}

			for (i = 0; i < 7; i++)
				motor_inhibit[i] = 5;
			// now loop checking slaves for new user input
			for (i = 0; i < 25;i++){
				for (y = 1; y < (grid_ysize + 1); y++){
					if (motor_inhibit[y])
						motor_inhibit[y]--;
					for (x = 1; x < (grid_xsize + 1); x++){

						// Read the user input from the slave.
						f = slave_cell_read(x - 1,y - 1);

						// If the inhibit timer is active, ignore any user input.
						if (!motor_inhibit[y]){
							if (f == cell_user_move){
								// Got new movement.
								cell_array[cell_new][x][y] = cell_alive;

								// Tell the cell to start moving
								slave_cell_write(x - 1,y - 1,cell_array[cell_new][x][y]);

								motor_inhibit[y] = 5;
							}
						}
					}
				}
			}

		}
	}

}