// calipers.c
// for NerdKits with ATmega168
// mike@nerdkits.com

#define F_CPU 14745600

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PB1 -- (inverted) data
// PB2 -- (inverted) clock
// PB3 -- power to calipers
// PB4 -- direction switch (pull up)
// PB5 -- zero button (pull up)
// PC1 -- data line MOSFET
// PC3 -- clock line MOSFET

void calipers_power_on() {
  PORTB |= (1<<PB3);
  DDRB |= (1<<PB3);
}

void calipers_touch_mode() {
  PORTC |= (1<<PC1);
  delay_ms(100);
  PORTC &= ~(1<<PC1);
}

void calipers_touch_zero() {
  PORTC |= (1<<PC3);
  delay_ms(100);
  PORTC &= ~(1<<PC3);
}

uint8_t read_bit() {
  // wait for rising edge of /CLK
  // then sample /DATA
  // return 0 for true 0 (high voltage on /DATA)
  // return 1 for true 1 (low voltage on /DATA)
  // return 2 for timeout error
  
  uint8_t attempt_counter = 200;
  // at 14.7456 MHz and 8 clocks/loop, 200 takes 108us.
  while(--attempt_counter) {
    if((PINB & (1<<PB2)) != 0) {
      // we got rising edge
      break;
    }
  }
  if(attempt_counter==0)
    return 2;  // timeout error
  
  // sample a bunch of times and take the best guess
  uint8_t samples = 5;
  uint8_t samples_0 = 0;
  uint8_t samples_1 = 0;
  uint8_t i;
  for(i=0; i<samples; i++) {
    if((PINB & (1<<PB1)) != 0) {
      samples_0++;
    } else {
      samples_1++;
    }
  }
  
  if(samples_0 > samples_1) {
    return 0;
  } else {
    return 1;
  }
}

int32_t read_bits() {
  // read 24 bits
  // LSB first
  // return in a signed int32_t
  
  uint32_t accumulator = 0;
  uint32_t bitval = 1;
  uint8_t rb;
  uint8_t i;
  for(i=0; i<24; i++) {
    rb = read_bit();
    if(rb==2) {
      // TIMEOUT
      return (1L<<30);
    } else if(rb==1) {
      accumulator |= bitval;
    }
    bitval <<= 1;
  }
  
  // handle negatives
  if(rb == 1) {
    // the MSB was 1 so we've got a negative number!
    // fill the upper 8-bits of the 32-bit
    accumulator |= 0xFF000000L;
  }
  
  return accumulator;
}

void wait_for_quiet() {
 // get at least 50us of high on /CLK
 // before returning.
 // not really microseconds -- close enough!
 uint8_t quiet_us = 0;
 while(quiet_us++ < 50) {
   if((PINB & (1<<PB2)) == 0) {
     // /CLK is low -- not quiet!
     quiet_us = 0;
   }
 }
 // we made it through 50!
 return;
}

void wait_for_pulse() {
  // get at least 50us of high on /CLK
  wait_for_quiet();
  
  // then wait for a falling edge
  while((PINB & (1<<PB2)) != 0) {
    // do nothing
  }
}

int main() {
  // activate pull-ups
  PORTB |= (1<<PB1) | (1<<PB2) | (1<<PB4) | (1<<PB5);

  // allow zero, mode pushing
  DDRC |= (1<<PC1) | (1<<PC3);

  // turn on calipers
  calipers_power_on();
  delay_ms(2000);
  calipers_touch_mode();
  delay_ms(100);
  calipers_touch_zero();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // lcd
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_clear_and_home();
  
  int32_t lastpos, tmpr, refpos = 0;
  uint8_t flipdir;
  double lastpos_inches, lastpos_mm;
  while(1) {
    wait_for_pulse();
    tmpr = read_bits();
    if(tmpr != (1L<<30)) {
    
      // zero?
      if((PINB & (1<<PB5)) == 0)
        refpos = tmpr;
      // alternate direction?
      flipdir = ((PINB & (1<<PB4)) == 0) ? 1 : 0;
        
    
      // calculate position
      lastpos = tmpr - refpos;
      if(flipdir) 
        lastpos = -lastpos;
      
      // translate into inches and mm
      lastpos_inches = lastpos / 20480.0;
      lastpos_mm = lastpos * (25.4 / 20480.0);
      
      // send over serial port
      printf_P(PSTR("%8ld\t%10.5f\r\n"), lastpos, lastpos_inches);
      
      // show on LCD
      lcd_line_one();
      fprintf_P(&lcd_stream, PSTR("Smithy 1220XL Z-Axis"));

      lcd_line_two();
      fprintf_P(&lcd_stream, PSTR("%9.4f inches    "), lastpos_inches);

      lcd_line_three();
      fprintf_P(&lcd_stream, PSTR("%8.3f  mm        "), lastpos_mm);

      lcd_line_four();
      if(!flipdir) {
        fprintf_P(&lcd_stream, PSTR("+ direction is DOWN "));
      } else {
        fprintf_P(&lcd_stream, PSTR("+ direction is UP   "));
      }
    }
  } 

  return 0;
}

