//==================================================================
//=====                                                        =====
//=====           PIC Based Data Acquisition System            =====
//=====              By: Steve Hageman 30Jul00                 =====
//=====               www.sonic.net/~shageman                  ===== 	
//=====                                                        =====	
//==================================================================
//=====	Copyright 2000, by Steven C. Hageman, This code can be =====
//===== used only for private use with the PIC DAS Hardware    =====
//===== as described in the January, 2001 QEX article.         =====
//===== All other rights reserved by Steven C. Hageman, 2000   =====
//===== For other uses please contact Steve Hageman at the web =====
//===== site listed above - Thanks, Steve Hageman              =====
//==================================================================
//=====      Written in CCS C PCM Compiler Version 2.685       =====
//==================================================================
//=====  Ver: 0.0 - Initial Writing - 10Jul00                  =====
//=====  Ver: 1.0 - Initial Release 16C63A Version - 25Aug00   =====
//==================================================================



//-----< Initialization code >---------------------------------------
// Firmware Version
#define VER_MAJ	1	// Major version	
#define VER_MIN 0	// Minor version
			

//-----< Include Files >-----
#include <16c63A.h>			// Target PIC include file
#device *=16				// Enable 16 bit pointers
#include <stdio.h>			// For RS232 functions
#include <string.h>			// For the string parsing functions
#include <stdlib.h>			// For the atol function


//-----< Initialize all RAM >-----
#ZERO_RAM					// Always do this, zeros all RAM at startup


//-----< Compiler use statements >-----
#use delay(clock=8000000, RESTART_WDT) 						// Clock setup	
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, RESTART_WDT) // Hardware UART
#use fast_io(b)		// Fast IO for port b


// Warning: The PUT seems to need to go last to end up ON in the HEX file!
#fuses HS, WDT, NOPROTECT, BROWNOUT, PUT					// Fuses configuration


//-----< General Program Defines >-----
#define DOOMSDAY		0			// Not here yet!
#define OFF				0			// Used for pin i/o
#define ON				1			// Used for pin i/o
#define MAX_BUFFER_LEN 15			// Length of RS232 buffer
#define FULL			0			// Used by RS232 xmit register (98h, bit1)
#define CR				0x0d		// ASCII Value for a Carriage Return
#define LF				0x0a		// ASCII value for a Line Feed
#define	SPACE			0x20		// ASCII Value for a Space
#define OK				1			// OK / NOT_OK
#define NOT_OK			0			


//-----< Port location defines >-----
#byte port_a = 0x05
#byte port_b = 0x06
#byte port_c = 0x07


//-----< Pin defines >-----

//...ADC SPI Interface
#bit ADC_SCLK	= port_a.0	// ADC Sclk
#bit ADC_CS		= port_a.1	// ADC Chip Select
#bit ADC_DIN	= port_a.2	// ADC Din	
#bit ADC_DOUT	= port_a.3	// ADC Dout

//...DAC SPI Interface
#bit DAC_SCLK	= port_c.3	// DAC Sclk
#bit DAC_DIN	= port_c.4	// DAC Din
#bit DAC_CS		= port_c.5	// DAC Chip Select


//-----< Global Variables >-----
long AdcValue;						// Result of ADC Conversion

int Index;							// DON'T MODIFY INDEX WITHOUT
									// CAREFULLY CHECKING ALL THE CODE
									// FIRST. THE PARAMETER ROUTINES
									// DEPEND ON THE VALUE BETWEEN
									// CALLS!

int CharBuffer[MAX_BUFFER_LEN+5];	// RS232 Character Buffer The +5 adds a worst case
									// 5 chars to the buffer to prevent an overrun.
									// This simplifies the parsing functions.


//-----< RS232 Packet definitions >-----
int RX_Command_Ready;		// TRUE If a receive packet is ready


//=====< Hardware Control Subroutines >===================================

void set_dac(int channel_num, long dac_value)
{
// MAX 525 Quad DAC SPI Routines
// Valid channel numbers are 0 through 3
// Channel numbers are mapped to the actual hardware port
// Valid DAC values are 0-4095

int ctr;
int prefix;

	// Map the physical DAC to the physical port
	// The data is sent MSB first, so the prefix is back filled with zeros
	// only the first 4 bits of the prefix are output.
	// The mode used updates all the DAC's at the end of the data sequence.
	switch( channel_num )
	{
		case 0:  // Physical DAC C
		{
			prefix = 0b10110000; 
			break;
		}
		case 1:  // Physical DAC D
		{
			prefix = 0b11110000; 
			break; 
		}
		case 2:  // Physical DAC A
		{
			prefix = 0b00110000; 
			break; 
		}
		case 3:  // Physical DAC B
		{	
			prefix = 0b01110000; 
			break; 
		}
		default: // Default to channel 0, DAC C
		{
			prefix = 0b10110000; 
			break; 
		}
	}

	// Setup Clock
	DAC_SCLK = 0;

	// Drop the DAC Chip Select
	DAC_CS = 0;

	//... Shift out preamble data (MSB First)
	for( ctr=0 ; ctr <=3 ; ctr++ )
	{
		DAC_DIN = shift_left(&prefix, 1, 0);
		DAC_SCLK = 1;
		DAC_SCLK = 0;	
	}

	//... Shift out the 12 bits of DAC data (MSB First)
	// First left justify the DAC value in the register
	// This also bounds the value to the range 0 - 4095
	dac_value = dac_value << 4;

	for( ctr=0 ; ctr <=11 ; ctr++ )
	{
		DAC_DIN = shift_left(&dac_value, 2, 0);
		DAC_SCLK = 1;
		DAC_SCLK = 0;	
	}

	// Release the Chip Select, this also updates all the DAC's
	DAC_CS = 1;
}


void get_adc(int channel_num)
{
// ... MAX186 Single ended ADC driver ...
// The ADC routine reads the specified channel, then returns
// the result via the RS232 connection.
// Note: Valid channel numbers are 0-7 for this application.
// The driver works for any single ended channel number
// Coding: 0 = 0 volts, 4096 = 4.096 Volts.
// Note: With a 8 MHz PIC clock this routine typically gets 
// a complete conversion cycle in 175 uSec.

int ctr;
int actual_bits;

	// Welcome to the MAX186! You will notice that MAXIM
	// chose to have the bit pattern that selects the channel
	// not actually match the bit's that get shifted into the 
	// part when operated in single ended mode!
	// i.e. to select channel 2 you must shift in a 4.
	
	// So lets unscramble the bits now! (Single ended only)
	switch(channel_num)
	{
		case 0: actual_bits = 0; break;	// CH0 Pin 1
		case 1: actual_bits = 1; break;	// CH1 Pin 2
		case 2: actual_bits = 4; break;	// CH2 Pin 3
		case 3: actual_bits = 5; break;	// CH3 Pin 4
		case 4: actual_bits = 2; break;	// CH4 Pin 5
		case 5: actual_bits = 3; break;	// CH5 Pin 6
		case 6: actual_bits = 6; break;	// CH6 Pin 7
		case 7: actual_bits = 7; break;	// CH7 Pin 8
		default:actual_bits = 0; break;	// CH0 - default
	}
			
	//----- Send configuration bits to ADC
	
	// Setup Clock
	ADC_SCLK = 0;

	// Drop the ADC enable
	ADC_CS = 0;
	
	// Start bit
	ADC_DIN = 1;
	ADC_SCLK = 1;
	ADC_SCLK = 0;
	
	// Channel select bits (sent LSB first)
	// Sel2 (LSB)
	ADC_DIN = shift_right(&actual_bits, 1, 0);
	ADC_SCLK = 1;
	ADC_SCLK = 0;		
	
	// Sel1
	ADC_DIN = shift_right(&actual_bits, 1, 0);
	ADC_SCLK = 1;
	ADC_SCLK = 0;
	
	// Sel0 (MSB)
	ADC_DIN = shift_right(&actual_bits, 1, 0);
	ADC_SCLK = 1;
	ADC_SCLK = 0;

	// UNI/~BIP Bit (Set to Unipolar mode here)
	ADC_DIN = 1;
	ADC_SCLK = 1;
	ADC_SCLK = 0;

	// SGL/~DIFF Bit (Set to single ended here)
	ADC_DIN = 1;
	ADC_SCLK = 1;
	ADC_SCLK = 0;

	// PD0/PD1 bits (Set to internal clock mode here)
	ADC_DIN = 1;	// PD1
	ADC_SCLK = 1;
	ADC_SCLK = 0;

	ADC_DIN = 0;	// PD0
	ADC_SCLK = 1;
	ADC_SCLK = 0;

	// Note; When the clock went low above the conversion cycle started!

	ADC_DIN = 0;	// Just leave ADC_DIN at zero
		
	// Raise the CS pin and wait for ADC to finish.
	ADC_CS = 1;
	delay_us(10);	
	
	//----- ADC conversion complete, shift out the data MSB first
	// Initialize the ADC value
	AdcValue = 0;
	
	// Drop the CS pin
	ADC_CS = 0;
	
	for(ctr = 0 ; ctr <= 15 ; ctr++)	
	{
		// Do a clock to get the next bit
		ADC_SCLK = 1;
		ADC_SCLK = 0;
		
		// Shift the bit into our conversion result reg
		shift_left(&AdcValue, 2, ADC_DOUT);
	}
	
	// Got the ADC result, but it is in a 16 bit word
	// so shift it back down by 4 bits (so it is a true 14 bits).
	AdcValue = AdcValue >> 4; 		
		
	// Pull ADC enable high
	ADC_CS = 1;

	// Set the clock and data line low now
	ADC_SCLK = 0;
	ADC_DIN = 0;

	// All done, the conversion result is in the global AdcValue;
}


//=====< Decode Command Strings >=========================================


//-----< Decode the command packet >-----

void decode_command()
{

char * tok_str;
int command_token;
char sep[2] = " ";		// Token separation char is a space
int param_1;
long param_2;


	// Get command token before strtok cuts up the CharBuffer
	command_token = CharBuffer[0] + CharBuffer[1];

	// Tokinize the input command
	tok_str = strtok(CharBuffer,sep);		// This should be the entire command token
	param_1 = (int)atol(strtok('\0',sep));	// Parameter 1
	param_2 = atol(strtok('\0',sep));		// Parameter 2

	// Some useful debugging commands
	//puts(" ");puts(tok_str);
	//printf("\r\nToken = %U  P1 = %U  P2 = %ld", command_token, param_1, param_2);
	
	// Very simple tokenizer - as long as no two tokens
	// 'add' up to the same value this works fine and it saves memory too!
	// This is good for a limited command set like we have here

	switch( command_token )
	{
		case 'D' + 'I': // D+I = 141 - Set Direction Register
		{
			set_tris_b(param_1);

			// Send a CR
			printf("\r");
		
			break;
		}
		
 		case 'I' + 'N': // I+N = 151 - Input Port Value
		{
			printf("%U\r", port_b);
			break;
		}

		case 'O' + 'U': // O+U = 164 - Output Port Value
		{
			
			port_b = param_1;

			// Send a CR 
			printf("\r");
			
			break;
		}

		case 'B' + 'S': // B+S = 149 - Bit Set
		{	
			// Bound param_1
			if(param_1 > 7)
				param_1 = 7;

			// Set the bit
			port_b |= (1 << param_1);

			// Send a CR
			printf("\r");
			break;
		}

		case 'B' + 'C': // B+C = 133 - Bit Clear
		{
			// Bound param_1
			if(param_1 > 7)
				param_1 = 7;
		
			// Clear the bit
			port_b &= ~(1<<Param_1);

			// Send a CR 
			printf("\r");

			break;
		}

		case 'B' + 'I': // B+I = 139 - Bit Input
		{
			// Bound the value
			if( param_1 > 7 )
				param_1 = 7;
				
			// Note: I re-use param_1 here as the port value!
			switch( param_1 )
			{
				case 0: param_1 = input(PIN_B0); break;
				case 1: param_1 = input(PIN_B1); break;
				case 2: param_1 = input(PIN_B2); break;
				case 3: param_1 = input(PIN_B3); break;
				case 4: param_1 = input(PIN_B4); break;
				case 5: param_1 = input(PIN_B5); break;
				case 6: param_1 = input(PIN_B6); break;
				case 7: param_1 = input(PIN_B7); break;
			}
			// Print the value
			printf("%U\r", param_1);
	
			break;
		}

		case 'P' + 'U': // P+U = 165 - Set Pull Up State
		{
			// Bound the value
			if( param_1 == 0 )
				port_b_pullups(FALSE);

			if( param_1 == 1 )
				port_b_pullups(TRUE);

			// Send a CR 
			printf("\r");

			break;
		}

		case 'A' + 'I': // A+I = 138 - Analog In
		{
			// Bound the channel number 
			if( param_1 > 7 )
				param_1 = 7;

			// Read the ADC
			get_adc(param_1);

			// Print the value
			printf("%lu\r", AdcValue);
			break;
		}

		case 'A' + 'O': // A+O = 144 - Analog Out
		{
			// Bound the channel number 
			if( param_1 > 3 )
				param_1 = 3;

			// Set the DAC
			set_dac(param_1, param_2);	
			// Send a CR 
			printf("\r");

			break;
		}

		case 'V' + 'E': // V+E = 155 - Firmware version
		{
			printf("Hagtronics PIC DAS - Version %U.%U\r", VER_MAJ, VER_MIN);
			break;
		}

		case 'D' + 'E' : // 137 - Debug test case
		{
			
			printf("Token = %U", command_token);
			printf("  P1 = %U", param_1);
			printf("  P2 = %lu\r", param_2);

			break;
		}

		default:
			// Error message command
			printf("Unknown Command\r");
	}
}

//=====< RS232 Subroutines >==============================================

//-----< Get a RS232 command packet >-----
// Note: This routine is interrupt driven, RS232 input takes priority
// over every other operation in the system.
//
// I figure that this routine takes about 45 clock cycles at worst case to
// complete. At 8 MHz clock this is about 23 uS. At 9600 Baud 1/3 of a bit 
// time is 1/9600 / 3 = 34 uSeconds. So the time is well under 1/3 of a bit time.
// So the analysis of this routine is that it is fast enough so as to not 
// miss anything.

#INT_RDA	// Interrupt driven RS232
void rs232_isr(void)
{
int temp;

	//*Note* This routine is time critical! Don't bloat it!
	// Get the char
	temp = getc();
	
	// If we got a CR, then the command is completed
	if (temp == CR)
	{
		CharBuffer[Index] = temp;
		RX_Command_Ready = TRUE;
		return;
	}

	// Ignore if the char is a Linefeed
	if (temp == LF)
		return;

	// Save the char as uppercase
	CharBuffer[Index] = toupper(temp);
	
	// Check for buffer overflow
	if ( Index >= (MAX_BUFFER_LEN - 1) )
		Index = 0;
	else
		Index++;
}


//=====< Main >======================================================

void main(void)
{

	//----- Setup Ports / Unused pins are set to output
	//    Bit#-> 76543210
	set_tris_a(0b00001000);	// ADC SPI Interface
	set_tris_b(0b11111111);	// GPIO Port (Initial setting, all inputs)
	set_tris_c(0b10000000);	// RS232 port and DAC SPI Interface
	
	port_b_pullups(TRUE);	// Default Is On

	//----- Initialize critical ports & things
	restart_wdt();
	delay_ms(200);	// Let everything powerup correctly

	//----- Initialize internal timer / counter 
	// WDT will be typically 2.3 Sec
	setup_counters(RTCC_INTERNAL, WDT_2304MS);

	//----- Initialize global variables
	RX_Command_Ready = FALSE;
	Index = 0;
	
	//----- Initialize port pins
	// Initialize all output pins to a known state
	ADC_SCLK = 0;
	ADC_DIN = 0;
	ADC_CS = 1;

	DAC_SCLK = 0;
	DAC_DIN = 0;
	DAC_CS = 1;

	port_b = 0;
	
	//----- Initialize Hardware
	restart_wdt();

	//----- Enable RS232 interrupts
	disable_interrupts(GLOBAL);	// Turn all off
	enable_interrupts(INT_RDA);	// Set the RS232 input interrupt
	enable_interrupts(GLOBAL);	// Now enable it

	//----- Main loop -----	
	while(!DOOMSDAY)
	{
		// Reset the WDT
		restart_wdt();

		// Check to see if a command is pending
		if ( RX_Command_Ready == TRUE )
		{

			// Just to be safe, first things first...
			disable_interrupts(GLOBAL);
			restart_wdt();
			RX_Command_Ready = FALSE;

			// Let's start to decode the command
			decode_command();

			// Done so set the RS232 on again
			Index = 0;
			enable_interrupts(GLOBAL);
		}
		
		
	} // End of main loop			


} //----- End of main -----


//----- Fini -----

