/* Teensy Serial to OGI Lumen Nixie Driver board  rev 0.1
 * 
 * Based on (read as "a quick hack of"):
 * Simple example for Teensy USB Development Board
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2008 PJRC.COM, LLC
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* 
 * Nixie Driver Pin	Teensy Pin
 * -------      	---------
 * 2  GND		GND
 * 5  Vcc		+5v
 * 1  SER		D4
 * 3  SCK		D5
 * 4  RCK		D6
 * 6  NC		-
 *
 * For 3d-ish effects:
 * Nixie digit order, front to back:  3894057621
 */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>
#include <util/delay.h>
#include "usb_serial.h"

#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
#define NIXIE_SER	(1 << 4)
#define NIXIE_SCK	(1 << 5)
#define NIXIE_RCK	(1 << 6)

void send_str(const char *s);
uint8_t recv_str(char *buf, uint8_t size);
void parse_and_execute_command(const char *buf, uint8_t num);

void nixie_init(void)
{
	PORTD &= ~(NIXIE_SER);
	PORTD &= ~(NIXIE_SCK);
	PORTD &= ~(NIXIE_RCK);
	DDRD |= (NIXIE_SER | NIXIE_SCK | NIXIE_RCK);
}

void nixie_send_digit(uint8_t digit)
{
	for (int i=0; i < 4; i++) {
		PORTD &= ~(NIXIE_SCK);	
		if ((digit & 0x8) != 0) {
			PORTD |= NIXIE_SER;
		} else {
			PORTD &= ~(NIXIE_SER);	
		}
		_delay_us(10);
		PORTD |= NIXIE_SCK;
		_delay_us(10);
		digit <<= 1;
	}
	PORTD &= ~(NIXIE_SCK);
	PORTD |= NIXIE_SER;
	_delay_us(10);
}

void nixie_show(void)
{
	PORTD |= NIXIE_RCK;
	_delay_us(10);
	PORTD &= ~(NIXIE_RCK);
	_delay_us(10);
}

int main(void)
{
	char buf[32];
	uint8_t n;

	// set for 16 MHz clock, and turn on the LED
	CPU_PRESCALE(0);

	// initialize the USB, and then wait for the host
	// to set configuration.  If the Teensy is powered
	// without a PC connected to the USB port, this 
	// will wait forever.
	usb_init();
	while (!usb_configured()) /* wait */ ;
	_delay_ms(1000);
	nixie_init();
	_delay_ms(2);
	nixie_send_digit(8);
	nixie_send_digit(7);
	nixie_send_digit(6);
	nixie_send_digit(5);
	nixie_send_digit(4);
	nixie_send_digit(3);
	nixie_send_digit(2);
	nixie_send_digit(1);
	nixie_show();

	while (1) {
		// wait for the user to run their terminal emulator program
		// which sets DTR to indicate it is ready to receive.
		while (!(usb_serial_get_control() & USB_SERIAL_DTR)) /* wait */ ;

		// discard anything that was received prior.  Sometimes the
		// operating system or other software will send a modem
		// "AT command", which can still be buffered.
		usb_serial_flush_input();

		// print a nice welcome message
		send_str(PSTR("\r\nTeensy USB Serial Example, "
			"Simple Pin Control Shell\r\n\r\n"
			"Example Commands\r\n"
			"  B0?   Read Port B, pin 0\r\n"
			"  C2=0  Write Port C, pin 1 LOW\r\n"
			"  D6=1  Write Port D, pin 6 HIGH  (D6 is LED pin)\r\n\r\n"));

		// and then listen for commands and process them
		while (1) {
			send_str(PSTR("> "));
			n = recv_str(buf, sizeof(buf));
			if (n == 255) break;
			send_str(PSTR("\r\n"));
			// parse_and_execute_command(buf, n);
		}
	}
}

// Send a string to the USB serial port.  The string must be in
// flash memory, using PSTR
//
void send_str(const char *s)
{
	char c;
	while (1) {
		c = pgm_read_byte(s++);
		if (!c) break;
		usb_serial_putchar(c);
	}
}

// Receive a string from the USB serial port.  The string is stored
// in the buffer and this function will not exceed the buffer size.
// A carriage return or newline completes the string, and is not
// stored into the buffer.
// The return value is the number of characters received, or 255 if
// the virtual serial connection was closed while waiting.
//
uint8_t recv_str(char *buf, uint8_t size)
{
	int16_t r;
	uint8_t count=0;

	while (count < size) {
		r = usb_serial_getchar();
		if (r != -1) {
			if (r == '\r' || r == '\n') {
				nixie_show();	
				return count;
			}
			if (r >= '0' && r <= '9') {
				*buf++ = r;
				usb_serial_putchar(r);
				nixie_send_digit(r);
				count++;
			} else if (r == ' ') {
				usb_serial_putchar(r);
				nixie_send_digit(10);
			}
		} else {
			if (!usb_configured() ||
			  !(usb_serial_get_control() & USB_SERIAL_DTR)) {
				// user no longer connected
				return 255;
			}
			// just a normal timeout, keep waiting
		}
	}
	return count;
}

// parse a user command and execute it, or print an error message
//
void parse_and_execute_command(const char *buf, uint8_t num)
{
	uint8_t port, pin, val;

	if (num < 3) {
		send_str(PSTR("unrecognized format, 3 chars min req'd\r\n"));
		return;
	}
	// first character is the port letter
	if (buf[0] >= 'A' && buf[0] <= 'F') {
		port = buf[0] - 'A';
	} else if (buf[0] >= 'a' && buf[0] <= 'f') {
		port = buf[0] - 'a';
	} else {
		send_str(PSTR("Unknown port \""));
		usb_serial_putchar(buf[0]);
		send_str(PSTR("\", must be A - F\r\n"));
		return;
	}
	// second character is the pin number
	if (buf[1] >= '0' && buf[1] <= '7') {
		pin = buf[1] - '0';
	} else {
		send_str(PSTR("Unknown pin \""));
		usb_serial_putchar(buf[0]);
		send_str(PSTR("\", must be 0 to 7\r\n"));
		return;
	}
	// if the third character is a question mark, read the pin
	if (buf[2] == '?') {
		// make the pin an input
		*(uint8_t *)(0x21 + port * 3) &= ~(1 << pin);
		// read the pin
		val = *(uint8_t *)(0x20 + port * 3) & (1 << pin);
		usb_serial_putchar(val ? '1' : '0');
		send_str(PSTR("\r\n"));
		return;
	}
	// if the third character is an equals sign, write the pin
	if (num >= 4 && buf[2] == '=') {
		if (buf[3] == '0') {
			// make the pin an output
			*(uint8_t *)(0x21 + port * 3) |= (1 << pin);
			// drive it low
			*(uint8_t *)(0x22 + port * 3) &= ~(1 << pin);
			return;
		} else if (buf[3] == '1') {
			// make the pin an output
			*(uint8_t *)(0x21 + port * 3) |= (1 << pin);
			// drive it high
			*(uint8_t *)(0x22 + port * 3) |= (1 << pin);
			return;
		} else {
			send_str(PSTR("Unknown value \""));
			usb_serial_putchar(buf[3]);
			send_str(PSTR("\", must be 0 or 1\r\n"));
			return;
		}
	}
	// otherwise, error message
	send_str(PSTR("Unknown command \""));
	usb_serial_putchar(buf[0]);
	send_str(PSTR("\", must be ? or =\r\n"));
}


