/*
 * DRO_GLCD.c <- This is the old name of the project, but a GLCD was to bad to read
 * So I decided to use a 7-segment display
 * Created: 10.11.2015 16:09:31
 *  Author: Peter Menges
 */ 
/***************************
 Be aware that in this application only the first 16 bits of the value the calipers sending are used.
 This is because the calipers used for my lathe are only 200 mm long.
 So 16 bits are sufficient to show values up to 655.35 mm in length.
 
****************************/

#include <avr/io.h>

#include "portdefs.h"


extern void Init_Timer(void);

extern volatile uint8_t key_press;
extern volatile uint8_t event;					// Encoder events

volatile uint8_t z_axis[CAL_LENGTH];				// 24 bits for the values
volatile uint8_t x_axis[CAL_LENGTH];				// sure, these are bytes, but faster access in the ISR!
							
static uint16_t pre_x=0, pre_z=0;							// values of presets

// Test 2.54 mm = 0011_0111_1001_0000_0000_1000 MINUS!

		
extern volatile uint8_t cal_sig;			

/* Display Buffer holding the BCD-Values */

/* First 5 bytes are for the upper segments, the remaining for the lower segs*/
/* The sign and the double (bit) information are stored in the first byte upper nibble*/

volatile uint8_t disp_data[DISP_BUF_MAX];		

uint16_t convtab[4]={10,100,1000,10000};

/* Rotary encoder reference*/
extern uint8_t rotary_process(void);

/* Do not call this routine when interrupts are enabled! */
/* remove leading zeros until first location before the decimal pointer
*/
	
void clear_leading_zero(void)
{
	
	uint8_t i;
	uint8_t volatile *p;
	

	p = &disp_data[0];
	for(i=0; i < 2;i++){
		if (((*p & 0x0f) == 0)){
			*p |= 0x0f;
		}
		else
		break;
		p++;
	}
	p = &disp_data[5];
	for(i=5; i < 7;i++){
		if (((*p & 0x0f) == 0)){
			*p |= 0x0f;
		}
		else
		break;
		p++;
	}
	
}
/* Params: *p pointer to the beginning of the disp_data buffer
           val = unsigned value to convert.
*/

void tobcd(volatile uint8_t *p, uint_fast16_t val){
	
	uint_fast8_t i,res;
	volatile uint8_t *bp;
	
	register uint8_t sreg;
	
	sreg = SREG;
	asm("cli");
	
	// first clear old values
	// keep signs and double informations
	bp = p;
	for(i=0; i < (DISP_BUF_MAX/2); i++)
			*bp++ &= 0xf0;
	
		
	i= ((DISP_BUF_MAX/2) - 1);		// Number of digits minus 1
	
	do {
		i--;
		
		for(res=0; val >= convtab[i]; val -= convtab[i]){
			res++;
		}
		*p |= res;
		p++;
		
		
	} while(i);
	*p |= val;
	
	clear_leading_zero();
	SREG = sreg;
}
/* The parameter 'which' gives the offset to the disp_data buffer
   and selects the axis buffer.
*/
uint16_t cal_to_int(uint8_t which)
{
		static volatile uint8_t *bp;
		static uint8_t i;
		uint16_t val;
		register uint8_t sreg;
	
		sreg = SREG;
		asm("cli");
		
		val = 0;
		
		if ( which == CAL_NEW_X){
			if ( pre_x == 0){
				if (x_axis[CAL_SIGN]==1)
				DISP_BUF_X |= SIGN_POS_MINUS;
				else
				DISP_BUF_X &= ~(SIGN_POS_MINUS);
			}
			bp = &x_axis[CAL_BIT16];
		}
			else {
				if ( pre_z == 0){
					if (z_axis[CAL_SIGN]==1)
					DISP_BUF_Z |= SIGN_POS_MINUS;
					else
					DISP_BUF_Z &= ~(SIGN_POS_MINUS);
				}
			
			bp = &z_axis[CAL_BIT16];
			}
		for(i=0; i < CAL_BIT16; i++){
			
			val |= *bp--;
			val <<= 1;
		}
		val |= *bp;
			
		SREG = sreg;
		return(val);
		
}	

void Init_Display(void)
{
	
		DISP_DATA_DDR |= DISP_DATA_DDR_MSK;
		DISP_CNTRL_DDR |= DISP_CNTRL_MSK;			// Change at 18.12.2015
		DISP_DATA = 0x00;						/* all off */
		DISP_CNTRL = 0x3f;					 /* Pull ups for the inputs on */
}
void disp_error(volatile uint8_t *buffer, uint8_t what){
	
	volatile uint8_t *bp;
	register uint8_t i;
	//asm("cli");

	bp = buffer;
	for(i=0; i < (DISP_BUF_MAX/2); i++)
			*bp++ = what;
	
	//asm("sei");
	
}
uint8_t encoder_get_and_clr( void )
{
	uint8_t r;
	
	asm("cli");
	r = event;
	event &= ~event;		// clear only that bit
	asm("sei");
	return r;
}
uint8_t get_key(void){

	uint8_t key;
	
	asm("cli");
	key = key_press;
	asm("sei");
	return key;
	
}
void clear_key(uint8_t val){
	
	asm("cli");
	key_press &= ~val;
	asm("sei");
}

int main(void)
{

	uint16_t x_val, z_val;
	uint8_t enc_input=0;
	uint8_t which=0;
	uint8_t key_val=0;
	uint16_t inp_val=0;
	volatile uint8_t *bp, *basep;
	int8_t i;
			
	Init_Display();
	
	Init_Timer();
	cal_sig = 0;		// Initial value

	
	
    do{

			/********** Z Axis ************/
			if ( cal_sig & CAL_NEW_Z){
						
					
					z_val = cal_to_int(CAL_NEW_Z);
					if ( DISP_BUF_Z & SIGN_POS_DOUBLE && pre_z == 0) // show value doubled
							z_val <<= 1;							 // But not if we have a preset
																	 // This applies for the z-axis only!
					if ( pre_z ){ // subtract caliper value from preset
						if ( pre_z < z_val)
							z_val = z_val - pre_z;
						else
							z_val = pre_z - z_val;	
							
					}		
					tobcd(&DISP_BUF_Z,z_val);
					cal_sig &= ~(CAL_NEW_Z);
					

			}
			if (cal_sig & CAL_INVALID_Z){ // Error condition Z!
			
					
					disp_error(&DISP_BUF_Z,0x0c);
					pre_z = 0;				// clear preset if any	
					cal_sig &= ~(CAL_INVALID_Z);	
			}
			if ( cal_sig & CAL_INCH_Z){	// INCHES! Bad!
				
					disp_error(&DISP_BUF_Z,0x0b);
					pre_z = 0;				// clear preset if any	
					cal_sig &= ~CAL_INCH_Z;
			}
			/********* X Axis ************/
			if ( cal_sig & CAL_NEW_X){
				
				x_val = cal_to_int(CAL_NEW_X);
				
					if ( DISP_BUF_X & SIGN_POS_DOUBLE ) // show the value doubled even if in preset mode
							x_val <<= 1;
							
					if ( pre_x ){ // subtract caliper value from preset
						if ( pre_x < x_val)
							x_val = x_val - pre_x;
						else
							x_val = pre_x - x_val;
							
					}					
				tobcd(&DISP_BUF_X,x_val);
				cal_sig &= ~(CAL_NEW_X);
			}	
			if ( cal_sig & CAL_INVALID_X){ // Error condition X!

				disp_error(&DISP_BUF_X,0x0c);
				pre_x = 0;		
				cal_sig &= ~(CAL_INVALID_X);
			}	
			if ( cal_sig & CAL_INCH_X){

				disp_error(&DISP_BUF_X,0x0b);
				pre_x = 0;
				cal_sig &= ~(CAL_INCH_X);
			}
			/**** KEY X Axis *********************/							
			if ( get_key() & KEY_X_MASK){
						DISP_BUF_X ^= SIGN_POS_DOUBLE; // Toggle this bit
						clear_key(KEY_X_MASK);
			}
			/**** KEY Z Axis *********************/	
			if ( get_key() & KEY_Z_MASK){
				if ( pre_z == 0)
				DISP_BUF_Z ^= SIGN_POS_DOUBLE;			// Toggle this bit too
				
				clear_key(KEY_Z_MASK);
			}
			/**************** SETUP of an axis **************/
			
			if ( get_key() & KEY_ENTER_MASK){			// Enter setup
				
					clear_key(KEY_ALL);
					disp_error(&DISP_BUF_Z,0x0f);		// darken displays (blank)
					disp_error(&DISP_BUF_X,0x0f);
					
					// Select axis by pressing the appropriate key 
					DISP_BUF_X |= (DISP_BLINK | SIGN_POS_MINUS);
					DISP_BUF_Z |= (DISP_BLINK | SIGN_POS_MINUS);	// blinking MINUS SIGNs for selection 
					do{
						key_val = get_key();
					} while (!(key_val & KEY_Z_MASK) && !(key_val & KEY_X_MASK));
					
					DISP_BUF_X &= ~(DISP_BLINK | SIGN_POS_MINUS); // clear it
					DISP_BUF_Z &= ~(DISP_BLINK | SIGN_POS_MINUS);
					
					if ( key_val & KEY_Z_MASK){
							bp = &DISP_BUF_Z;
							which = KEY_Z_MASK;
					}else {
							bp = &DISP_BUF_X;
							which = KEY_X_MASK;
					}
					clear_key(key_val);					// Reset
					basep = bp;							// Remember the base address of that pointer
					disp_error(bp,0x00);				// set all to null
					bp = bp + 4;						// End of String -> first input field
					*bp |= DISP_BLINK;					//
					i=0;
					do {
													
							enc_input =  encoder_get_and_clr();	// get the encoder state
									
							if ( enc_input == DIR_CW && *bp < 0x89){ // 0x80 equals blinking!
										(*bp)++;
							}
							if ( enc_input == DIR_CCW && *bp > 0x80)
										(*bp)--;
							
							if ( (get_key() & KEY_ENTER_MASK)){
								
										clear_key(KEY_ENTER_MASK);
										
										if ( bp > basep ){		// round robin 
											*bp &= ~DISP_BLINK;	// Clear blinking
											--bp;
											if (*bp == 0x0f )	// free field?
													*bp = 0x00;	// yes
													
											*bp |= DISP_BLINK;	// set blinking
											
										} else {
												*bp &= ~DISP_BLINK;	// clear last
												bp = basep +  4;	// first field
												*bp |= DISP_BLINK;
										}
							
							}
							
					} while (!(get_key() & which));
					
					clear_key(which);
					*bp &= ~DISP_BLINK;		// Clear blinking bit in last input field
					// Now convert the input into uint16_t format */
					bp = basep;
					inp_val = 0;
					for(i=0; i < 5; i++){
					
								inp_val += (*bp & 0x0f);		// clear any bits in upper nibble if any
								bp++;;
							if ( i < 4)
								inp_val *= 10;

					}
					
					if ( which == KEY_X_MASK){
						
							pre_x = inp_val;
								if ( pre_x )
								DISP_BUF_X |= (DISP_BLINK | SIGN_POS_MINUS);
								else
								DISP_BUF_X &= ~(DISP_BLINK |SIGN_POS_MINUS);
							} else {
								
							pre_z = inp_val;
							if ( pre_z)
							DISP_BUF_Z |= (DISP_BLINK | SIGN_POS_MINUS);
							else
							DISP_BUF_Z &= ~(DISP_BLINK | SIGN_POS_MINUS);
					}
					
					
			} // Setup end
			
	}while(1);
      
   
}