// File: curlycrt.c
//
// This is the control program for the CurlyCart, which is a
// modified Fisher-Price Power Wheels ride-on toy.
// The program is meant for a PIC 16F874 microcontroller.
// This program uses I2C to interface with a Microchip
// 256Kbit Serial EEPROM, which stores movements and allows them
// to be retrieved later.
//  
// For full details of the CurlyCart, refer to:
// http://www.media.mit.edu/~spiegel/CurlyCart
//
// Matthew Lee
// mattlee@media.mit.edu
// March 7, 2001
//
// Program written by Matthew Lee and Adam Smith
//
#include <16F874.H>

// Configure PIC to use: HS clock, Watchdog Timer, 
// no code protection, enable Power Up Timer
//
#fuses HS,NOWDT,NOPROTECT,PUT,NOBROWNOUT

// Tell compiler clock is 10MHz.  This is required for delay_ms()
// and for all serial I/O (such as printf(...).  These functions
// use software delay loops, so the compiler needs to know the
// processor speed.
//
#use DELAY(clock=10000000)

// Declare that we'll manually establish the data direction of
// each I/O pin on port B.
//
#use fast_io(A)
#use fast_io(B)
#use fast_io(D)
#use fast_io(E)

// definitions for the curly cart
//
#define MOTORLEFT_FWD   PIN_B1  
#define MOTORLEFT_REV   PIN_E1  
#define MOTORRIGHT_FWD  PIN_B0  
#define MOTORRIGHT_REV  PIN_B4  

#define STICKLEFT_FWD   PIN_A1  
#define STICKLEFT_REV   PIN_A2  
#define STICKRIGHT_FWD  PIN_A3  
#define STICKRIGHT_REV  PIN_A4  
#define GAS_PEDAL    PIN_A5  
#define MODE_BUTTON  PIN_E0

#define SERIALRAM_CLK   PIN_C6
#define SERIALRAM_DATA  PIN_C7

#define RED_LED      PIN_B5
#define RECORD_LED   PIN_B6
#define PLAY_LED  PIN_B7

// Macros to simplify I/O operations
//
#define RED_LED_ON      output_low(RED_LED)
#define RED_LED_OFF     output_high(RED_LED)
#define RECORD_LED_ON   output_high(RECORD_LED)
#define RECORD_LED_OFF  output_low(RECORD_LED)
#define PLAY_LED_ON     output_high(PLAY_LED)
#define PLAY_LED_OFF    output_low(PLAY_LED)

// state defines for the three run states
//
#define STATE_NORMAL    0
#define STATE_RECORD    1
#define STATE_PLAYBACK  2

#define MAX_SAMPLES 65535

// Default tri-state port direction bits: all PORT B bits are
// output except for IR_SENSOR (bit 4) and RC232_RCV (bit 5).
//
#define IRX_A_TRIS      0b11111110
#define IRX_B_TRIS      0b00000000
#define IRX_D_TRIS      0b00000000
#define IRX_E_TRIS      0b00000001

int debounce;
byte curlyState = STATE_NORMAL;
long sampleCount = 0;
long currentSample = 0;

#inline
void left_motor_stop(void)
{
  output_low(MOTORLEFT_FWD);
  output_low(MOTORLEFT_REV);
}

#inline
void left_motor_fwd(void)
{
  output_low(MOTORLEFT_REV);
  output_high(MOTORLEFT_FWD);
}

#inline
void left_motor_rev(void)
{
  output_low(MOTORLEFT_FWD);
  output_high(MOTORLEFT_REV);
}

#inline
void right_motor_stop(void)
{
  output_low(MOTORRIGHT_FWD);
  output_low(MOTORRIGHT_REV);
}

#inline
void right_motor_fwd(void)
{
  output_low(MOTORRIGHT_REV);
  output_high(MOTORRIGHT_FWD);
}

#inline
void right_motor_rev(void)
{
  output_low(MOTORRIGHT_FWD);
  output_high(MOTORRIGHT_REV);
}

void all_off(void)
{
  left_motor_stop();
  right_motor_stop();
  RED_LED_OFF;
  RECORD_LED_OFF;
  PLAY_LED_OFF;
}

byte get_stick_positions(void)
{
  byte result;
  /*
  if (!input(GAS_PEDAL))
  {
    return 0;
  }
  */
  result = 0xF0;
  if (input(STICKLEFT_FWD)) result += 0x01;
  else if (input(STICKLEFT_REV)) result += 0x02;
  if (input(STICKRIGHT_FWD)) result += 0x04;
  else if (input(STICKRIGHT_REV)) result += 0x08;
  return result;
}

void perform_motor_actions(byte input)
{
  /*
  if (!input(GAS_PEDAL))
  {
    left_motor_stop();
    right_motor_stop();
    return;
  }
  */
  if (input & 0x01)
  {
    left_motor_fwd();
  }
  else if (input & 0x02)
  {
    left_motor_rev();
  }
  else
  {
    left_motor_stop();
  }
  if (input & 0x04)
  {
    right_motor_fwd();
  }
  else if (input & 0x08)
  {
    right_motor_rev();
  }
  else
  {
    right_motor_stop();
  }
}

#use i2c(MASTER, SDA=SERIALRAM_DATA, SCL=SERIALRAM_CLK, FORCE_SW)

void init_ram() {
   output_high(SERIALRAM_DATA);
   output_high(SERIALRAM_CLK);
}

void write_ram(long address, byte data) {
   byte b;
   i2c_start();
   i2c_write(0b10100000);
   b = (byte)((address >> 8) & 0xFF);
   i2c_write(b);
   b = (byte)(address & 0xFF);
   i2c_write(b);
   i2c_write(data);
   i2c_stop();
}

void write_ram_old(byte addresshi, byte addresslo, byte data) {
   i2c_start();
   i2c_write(0b10100000);
   i2c_write(addresshi);
   i2c_write(addresslo);
   i2c_write(data);
   i2c_stop();
}

byte read_ram(long address) {
   byte data;

   byte b;
   i2c_start();
   i2c_write(0b10100000);
   b = (byte)((address >> 8) & 0xFF);
   i2c_write(b);
   b = (byte)(address & 0xFF);
   i2c_write(b);
   i2c_start();
   i2c_write(0b10100001);
   data=i2c_read();
   i2c_stop();
   return(data);
}

#inline
void check_mode_button()
{
  if (debounce > 0)
  {
    debounce--;
    return;
  }
  if (input(MODE_BUTTON))
  {
    delay_ms(50);
    while(input(MODE_BUTTON))
    {
      delay_ms(10);
    }
    debounce = 10;
    curlyState++;
    if (curlyState > STATE_PLAYBACK) curlyState = STATE_NORMAL;
    if (curlyState == STATE_RECORD)
    {
      sampleCount = 0;
      currentSample = 0;
    }
    else if (curlyState == STATE_PLAYBACK)
    {
      currentSample = 0;
    }
  }
}

void display_mode_indicators()
{
    if (curlyState == STATE_NORMAL)
    {
      RECORD_LED_OFF;
      PLAY_LED_OFF;
    }
    else if (curlyState == STATE_RECORD)
    {
      RECORD_LED_ON;
      PLAY_LED_OFF;
    }
    else if (curlyState == STATE_PLAYBACK)
    {
      RECORD_LED_OFF;
      PLAY_LED_ON;
    }
}

void main() {
  byte inputbyte;
  byte heartbeat;
  // since we've declared #use fast_io(B) (above), we MUST 
  // include a call to set_tris_b() at startup.
  // 
  set_tris_a(IRX_A_TRIS);
  set_tris_b(IRX_B_TRIS);
  set_tris_d(IRX_D_TRIS);
  set_tris_e(IRX_E_TRIS);
  setup_port_a(NO_ANALOGS);
  port_b_pullups(TRUE);

  all_off();

  RED_LED_ON;                    // reality check at startup
  RECORD_LED_ON;
  PLAY_LED_ON;
  left_motor_fwd();
  delay_ms(250);
  left_motor_rev();
  delay_ms(250);
  left_motor_stop();
  right_motor_fwd();
  delay_ms(250);
  right_motor_rev();
  delay_ms(250);
  right_motor_stop();
  RED_LED_OFF;
  RECORD_LED_OFF;
  PLAY_LED_OFF;
  delay_ms(500);

  init_ram();

  while (1) {
    check_mode_button();
    display_mode_indicators();
    inputbyte = get_stick_positions();
    if (curlyState == STATE_NORMAL)
    {
      perform_motor_actions(inputbyte);
    }
    else if (curlyState == STATE_RECORD)
    {
      perform_motor_actions(inputbyte);
      write_ram(sampleCount, inputbyte);
      sampleCount++;
      if (sampleCount >= MAX_SAMPLES)
      {
        curlyState = STATE_PLAYBACK;
        currentSample = 0;
      }
    }
    else if (curlyState == STATE_PLAYBACK)
    {
      inputbyte = read_ram(currentSample);
      if ((inputbyte & 0xF0) == 0xF0)
        perform_motor_actions(inputbyte);
      currentSample++;
      if (currentSample >= sampleCount)
        currentSample = 0;
    }
    if (heartbeat == 0)
    {
      RED_LED_ON; heartbeat = 1;
    }
    else
    {
      RED_LED_OFF; heartbeat = 0;
    }
    delay_ms(50);
  }
}