
/*
********************************************************************************
*	
*   Copyright (c) 2013 by Industrial Control Communications, Inc.
*
*   This software is copyrighted by and is the sole property of
*   Industrial Control Communications, Inc.  Any unauthorized use,
*   duplication, transmission, distribution, or disclosure of this
*   software is expressly forbidden.
*
*   This Copyright notice may not be removed or modified without prior
*   written consent of Industrial Control Communications, Inc.
*
*   ICC, Inc.
*   1600 Aspen Commons                      USA 608.831.1255
*   Suite 210                               http://www.iccdesigns.com
*   Middleton, WI 53562                     support@iccdesigns.com
*
********************************************************************************
*
*          Project: PIC18F SPI Master Example
*        File Name: spi.c
*    Original Date: 11.21.2013
*           Author: Josh Schulze
*
*      Description: This example shows how to communicate to the ICC PicoPort
*					using the SPI interface of a PIC18F microcontroller.
*					The specific microcontroller used in this example is the 
*					PIC18F87J50 FS USB Demo Board. The microcontroller is set 
*					up to use a 2MHz main clock with the SPI clock set to 500KHz.
*
*					The pins used in this example on the PIC18F87J50 are as follows:
*					RB2	: CS
*					RC3	: SCK
*					RC4	: MISO (SDI)
*					RC5	: MOSI (SDO)
*
*					Note that if the main clock frequency is changed in this
*					example, the DelayXTCYx calls will need to be modified.
*					Additionally, a delay between calls to the SpiTransfer function
*					may be required as well as a delay between calls to WriteSPI
*					within the SpiTransfer function. Any changes made are  
*					to comply with the PicoPort's SPI Timing Parameters found in
*					the PicoPort's datasheet.
*
*   Edit Date           Edit Description
*   ===============     ========================================================
*   
*
********************************************************************************
*/

/*
********************************************************************************
*                                   INCLUDES
********************************************************************************
*/

#include <p18cxxx.h>
#include <spi.h>
#include <delays.h>

/*
********************************************************************************
*                               CONFIGURATION BITS
********************************************************************************
*/

/* 
* These configuration bits are specific to the PIC18F87J50. These settings must
* be adjusted if using a different PIC microcontroller. The following
* configuration bits set up the main clock to be 2MHz.
*/
#pragma config XINST    = OFF   	// Extended instruction set
#pragma config STVREN   = ON      	// Stack overflow reset
#pragma config PLLDIV   = 3         // (12 MHz crystal used on this board)
#pragma config WDTEN    = OFF      	// Watch Dog Timer (WDT)
#pragma config CP0      = OFF      	// Code protect
#pragma config CPUDIV   = OSC4_PLL6 // OSC4_PLL6 = divide by 6 mode
#pragma config IESO     = OFF      	// Internal External (clock) Switchover
#pragma config FCMEN    = OFF      	// Fail Safe Clock Monitor
#pragma config FOSC     = HS     	// HS oscillator
#pragma config WDTPS    = 32768		// Watchdog Timer Postscaler
#pragma config MSSPMSK  = MSK5		// 5-Bit Address Masking mode
#pragma config CCP2MX   = DEFAULT	// ECCP1 outputs (P1B/P1C) are multiplexed with RE6 and RE5; ECCP3 outputs (P3B/P3C) are multiplexed with RE4 and RE3.

/*
********************************************************************************
*                                 LOCAL MACROS
********************************************************************************
*/

/* --- Standard Macros --- */
#ifndef BOOL
#define BOOL				unsigned char
#endif

#ifndef TRUE
#define TRUE				1
#endif

#ifndef FALSE
#define FALSE				0
#endif

#ifndef BYTE
#define BYTE				unsigned char
#endif

/* --- ICC SPI Macros --- */
#define ICC_SPI_SIZE		5

/* --- ICC SPI Commands --- */
#define GET_STATUS_CMD		0x01
#define SET_ADDRESS_CMD		0x11
#define READ_BYTE_CMD		0x21
#define READ_SHORT_CMD		0x22
#define READ_LONG_CMD		0x24
#define WRITE_BYTE_CMD		0x41
#define WRITE_SHORT_CMD		0x42
#define WRITE_LONG_CMD		0x44

/* --- Chip Select Macros --- */
#define CS_TRIS				TRISBbits.TRISB2
#define CS_LAT				LATBbits.LATB2

/*
********************************************************************************
*                                LOCAL TYPE DEFS
********************************************************************************
*/

typedef enum{
	RESET_STATE,
	BUSY_STATE,
	READY_STATE,
	OP_COMPLETE_STATE
} ICC_SPI_STATES;

typedef union{
	BYTE byte;

	struct{
		BOOL 			ack:1;
		BOOL 			err:1;
		BYTE 			reserved:4;
		BYTE		 	state:2;
	};
} ICC_SPI_STATUS;

/*
********************************************************************************
*                              PRIVATE PROTOTYPES
********************************************************************************
*/

static BOOL SpiTransfer(BYTE*, BYTE*, BYTE, BOOL);
static void BuildGetStatus(BYTE*);
static void BuildSetAddress(BYTE*, unsigned short);
static void BuildRead(BYTE*, BYTE);
static void BuildWrite(BYTE*, BYTE, unsigned long);
static unsigned long GetData(BYTE*);
static BOOL IsStatusValid(ICC_SPI_STATUS*);

/*
********************************************************************************
*                               PUBLIC FUNCTIONS
********************************************************************************
*/

/*
********************************************************************************
* Function    : main
* Description : Main entry point.
*				This function toggles the byte value of address 100 on the
*				PicoPort by communicating using SPI.
* Arguments   : None
* Returns     : None
* Comments    : 
********************************************************************************
*/
void main(void){
	BYTE sendBuffer[ICC_SPI_SIZE];
	BYTE recvBuffer[ICC_SPI_SIZE];
	ICC_SPI_STATUS *pStatus;
	unsigned long data;


	pStatus = (ICC_SPI_STATUS*)&recvBuffer[0];						// Initialize our status pointer

	CS_LAT  = 1;													// Configure CS pin as output and deactivate
	CS_TRIS = 0;

	OpenSPI(SPI_FOSC_4, MODE_00, SMPMID);							// Initialize the SPI master at 1/4 the main clock, in SPI Mode 0, sampling input data in the middle of output data

	while(1)
	{
		// Get the status of the PicoPort
		BuildGetStatus(sendBuffer);
		SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);

		// Set the address we want to access
		BuildSetAddress(sendBuffer, 100);							// Build the command to set address to 100
		SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);

		// Wait until the set address command is processed
		BuildGetStatus(sendBuffer);
		SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);

		// Send the read command to get the current value
		BuildRead(sendBuffer, 1);									// Build the command to read one byte
		SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);

		// Wait until the read is complete
		BuildGetStatus(sendBuffer);
		SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);

		if(pStatus->state == OP_COMPLETE_STATE)						// Confirm the PicoPort is in the Operation Complete State
		{
			data = GetData(recvBuffer);								// Get the data read or the error code

			if(!pStatus->err)										// Check if there were any errors
			{
				data = ~data;										// Toggle the data
	
				// Set the address again, so the PicoPort will accept a write
				BuildSetAddress(sendBuffer, 100);					// Build the command to set address to 100
				SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);
		
				// Wait until the set address command is processed
				BuildGetStatus(sendBuffer);
				SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);
		
				// Send the write command to write a new value
				BuildWrite(sendBuffer, 1, data);					// Build the command to write one byte
				SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);
		
				// Wait until the write is complete
				BuildGetStatus(sendBuffer);							// Build the command
				SpiTransfer(sendBuffer, recvBuffer, ICC_SPI_SIZE, TRUE);
			}
			else
			{
				// data variable contains the error code
			}
		}

		// Delay before repeating
		Delay10KTCYx(50);
	}
}

/*
********************************************************************************
*                               PRIVATE FUNCTIONS
********************************************************************************
*/

/*
********************************************************************************
* Function    : SpiTransfer
* Description : Initiates an SPI transfer which sends data in the sendBuf,
*				and puts the data received in the recvBuf
* Arguments   : BYTE*	: Send data buffer
*				BYTE*	: Receive data buffer
*				BYTE	: The number of bytes to send/receive
*				BOOL	: Whether or not this function should retry until the transfer is acknowledged
* Returns     : BOOL	: TRUE if successful
*						  FALSE otherwise
* Comments    : 
********************************************************************************
*/
static BOOL SpiTransfer(BYTE *sendBuf, BYTE *recvBuf, BYTE length, BOOL retryOnNak)
{
	BYTE i;
	BOOL success;
	ICC_SPI_STATUS *pStatus;


	pStatus = (ICC_SPI_STATUS*)&recvBuf[0];					// Initialize our status pointer
	
	do
	{
		success = TRUE;
		CS_LAT = 0;											// Activate the chip select
		for(i = 0; ((i < length) && success); i++)
		{
			if(WriteSPI(sendBuf[i]) == 0)					// Check if a write collision occurs
			{
				if(DataRdySPI())							// Make sure data is available
				{
					recvBuf[i] = SSPBUF;					// if so, put it in our receive buffer
				}
				else
				{
					success = FALSE;						// indicate an error has occurred
				}
			}
			else
			{
				success = FALSE;							// indicate an error has occurred
			}
		}	
		CS_LAT = 1;											// Deactivate the chip select

		if(success)											// If we've succeeded, check the status byte
		{
			if(!IsStatusValid(pStatus))
			{
				success = FALSE;							// Bad status, PicoPort may not be initialized yet
				Delay10KTCYx(4);							// Wait about 20ms before we retry
			}
		}
		if((!success || !pStatus->ack) && retryOnNak)
			Delay1KTCYx(1);									// Delay if we need to retry
	} while((!success || !pStatus->ack) && retryOnNak);

	return success;
}

/*
********************************************************************************
* Function    : BuildGetStatus
* Description : Populates a Get Status request into a buffer
* Arguments   : BYTE*	: The buffer to put the request in
* Returns     : None
* Comments    : 
********************************************************************************
*/
static void BuildGetStatus(BYTE *buffer)
{
	buffer[0] = GET_STATUS_CMD;
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	buffer[4] = 0x00;
}

/*
********************************************************************************
* Function    : BuildSetAddress
* Description : Populates a Set Address request into a buffer
* Arguments   : BYTE*			: The buffer to put the request in
*				unsigned short	: The address to use
* Returns     : None
* Comments    : 
********************************************************************************
*/
static void BuildSetAddress(BYTE *buffer, unsigned short address)
{
	buffer[0] = SET_ADDRESS_CMD;
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = (BYTE)(address >> 8);
	buffer[4] = (BYTE)address;
}

/*
********************************************************************************
* Function    : BuildRead
* Description : Populates a Read request of a given length into a buffer
* Arguments   : BYTE*	: The buffer to put the request in
*				BYTE	: The number of bytes to read
*						  This must be either 1, 2, or 4
* Returns     : None
* Comments    : 
********************************************************************************
*/
static void BuildRead(BYTE *buffer, BYTE numBytes)
{
	switch(numBytes)
	{
		case 1:
			buffer[0] = READ_BYTE_CMD;
			break;
		case 2:
			buffer[0] = READ_SHORT_CMD;
			break;
		default:
			buffer[0] = READ_LONG_CMD;
			break;
	}

	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	buffer[4] = 0x00;
}

/*
********************************************************************************
* Function    : BuildWrite
* Description : Populates a Write request of a given length into a buffer
* Arguments   : BYTE*			: The buffer to put the request in
*				BYTE			: The number of bytes to write
*						  		  This must be either 1, 2, or 4
*				unsigned long	: The data to write
* Returns     : None
* Comments    : 
********************************************************************************
*/
static void BuildWrite(BYTE *buffer, BYTE numBytes, unsigned long writeData)
{
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	buffer[4] = 0x00;

	switch(numBytes)
	{
		case 1:
			buffer[0] = WRITE_BYTE_CMD;
			buffer[4] = (BYTE)writeData;
			break;
		case 2:
			buffer[0] = WRITE_SHORT_CMD;
			buffer[3] = (BYTE)(writeData >> 8);
			buffer[4] = (BYTE)writeData;
			break;
		default:
			buffer[0] = WRITE_LONG_CMD;
			buffer[1] = (BYTE)(writeData >> 24);
			buffer[2] = (BYTE)(writeData >> 16);
			buffer[3] = (BYTE)(writeData >> 8);
			buffer[4] = (BYTE)writeData;
			break;
	}
}

/*
********************************************************************************
* Function    : GetData
* Description : Retrieves the data from an SPI packet
* Arguments   : BYTE*			: The buffer to get the data from
* Returns     : unsigned long	: The data contained in the buffer
* Comments    : 
********************************************************************************
*/
static unsigned long GetData(BYTE *buffer)
{
	unsigned long data;

	
	data = 0;
	data |= (((unsigned long)buffer[1]) << 24);
	data |= (((unsigned long)buffer[2]) << 16);
	data |= (((unsigned long)buffer[3]) << 8);
	data |= (unsigned long)buffer[4];

	return data;
}

/*
********************************************************************************
* Function    : IsStatusValid
* Description : Checks if the SPI Status byte is valid
* Arguments   : ICC_SPI_STATUS*	: Points to the ICC_SPI_STATUS byte
* Returns     : BOOL	: TRUE if the status byte is valid
*						  FALSE otherwise
* Comments    : 
********************************************************************************
*/
static BOOL IsStatusValid(ICC_SPI_STATUS *pStatus)
{
	return ((pStatus->reserved == 0) &&
			(((pStatus->ack == 1) && (pStatus->state != BUSY_STATE)) ||
			((pStatus->ack == 0) && (pStatus->state == BUSY_STATE))) &&
			(((pStatus->err == 0) && (pStatus->state != OP_COMPLETE_STATE)) ||
			(pStatus->state == OP_COMPLETE_STATE)));
}
