// RKS_Encoder.c
//
// Encodes 4-bit 14-state Gray code of Telefunken Rollkugel RKS 100-86 
// into 2-bit quadrature code. Output is fed to a USB ball mouse controller.
//
// Translates one Gray encoder step into SCALE quadrature steps,
// to compensate for the low resolution of the RKS. 
//
// Simulates right-click when the RKS's single button is pressed for > 0.7s
// while the mouse is stationary.
// (Idea from "One Finger Snap" for the Mac, http://www.old-jewel.com/onefingersnap)
//
// jm 6.11.2011
//
//
// For ATtiny 2313.
// Assumes internal RC osciallator @ 8MHz, system clock prescale factor 8,
// for 1 MHz CPU and IO clock (fuse settings).
//
// Connections to RKS 100-86:
// PB0..3	Gray code X0..X3	Input
// PB4..7	Gray code Y0..Y3	Input
// PD6		Button 				Input
//
// Connections to USB mechanical mouse:
// PD0..1	Quadrature X0, X1	Output
// PD2..3	Quadrature Y0, Y1	Output
// PD4		Left Button			Output
// PD5		Right Button		Output 



#include <ctype.h>

#include <stdint.h>
#include <stdbool.h>

#include <avr/io.h>

#include <avr/interrupt.h>


// ---------------------------------------
// hardware port definitions

// Gray code input port
#define GRAY_DDR	DDRB		// DDR 
#define GRAY_PORT	PORTB		// configure pullups
#define GRAY_PIN	PINB		// input port for reading

// Mouse quadrature and button output, RKS button input
#define MOUSE_DDR	DDRD		// DDR 
#define MOUSE_PORT	PORTD		// output port, configure pullups for input
#define MOUSE_PIN	PIND		// input port for reading buttons
#define MOUSE_RKSBUTTON 0x40	// mask for reading RKS button

// Spare output, used for timer "heartbeat" output
#define SPARE_DDR	DDRA
#define	SPARE_PORT	PORTA


// ---------------------------------------
// Mouse and button usage parameters

// mouse DPI translation, to deal with low DPI of RKS 
#define SCALE 3					// one step of RKS translates into SCALE steps of mouse output

// tolerance for small mouse movements while holding down the button for right click
#define MOVE_THRESHOLD 1

// timing definitions
#define TIME_RATE 100			// interrupt rate (Hz)
#define TIME_PRESS 70			// minimum duration (timer ticks) for button press to be considered a "right click"


// ---------------------------------------
// Global variables and types

// Allowed states and sequence for the Gray inputs.
// 0 represents a closed contact.
//
#define INMAX 13
unsigned const char instate[INMAX+1] = {
	0b1011, 0b1001, 0b1101, 0b0101, 0b0001, 0b0011, 0b0111, 
	0b0110, 0b0010, 0b0000, 0b0100, 0b1100, 0b1000, 0b1010
};

// Sequence for quadrature outputs
//
#define OUTMAX 3
unsigned const char outstate[OUTMAX+1] = {
	0b00, 0b01, 0b11, 0b10
};

// states for mouse button simulation
//
typedef enum {
	but_open, 	// RKS button not pressed
	but_timer, 	// RKS button pressed, waiting whether long enough for right click
	but_drag, 	// mouse movement detected during button press > dragging, no right click
	but_right	// reached timeout for stationary press - now simulating right click
} but_state;


// increment and decrement modulo (max+1)
#define inc(i,max) (i==max? 0 : i+1)
#define dec(i,max) (i? i-1 : max)


// ---------------------------------------
// timer to monitor long button press

volatile unsigned int timer;	// timer counter (timer ticks). 
								// May overflow; is reset to 0 and polled from main loop.

// initialize timer 	
//
void timer_init( void )
{
	// Initialization for Timer1
	TCCR1A = 0;										// CTC, XTAL / 64
	TCCR1B = 1<<WGM12 ^ 1<<CS11 ^ 1<<CS10;			// CTC, XTAL / 64
	OCR1A = (unsigned int) (F_CPU / (64*TIME_RATE));// interrupt period
	TIMSK |= 1<<OCIE1A;

	SPARE_DDR = 0xFF;			// all outputs
	SPARE_PORT = 0;				// output zeros
	
	timer = 0;
}


// Timer interrupt routine
//
ISR (TIMER1_COMPA_vect)
{
	timer++;
	SPARE_PORT = timer & 0x01;
}


// ---------------------------------------
// main program

int main(void)
{
	unsigned short xIn=0, yIn=0;			// Gray input states
	unsigned short xOut=0, yOut=0;			// Quadrature output states
	int xCount=0, yCount=0;					// pending steps of mouse output

	unsigned short lButton=1, rButton=1;	// buttons (not pressed == 1)
	but_state buttonState = but_open;
	int xMoved=0, yMoved=0;					// how far has the mouse moved since button was pressed?

	unsigned short u;						// temp - current gray input/button input


	// initialize IO and global variables

	GRAY_DDR = 0;		// all input pins
	GRAY_PORT = 0;		// no pullups

	MOUSE_DDR = 0xFF ^ MOUSE_RKSBUTTON;		// all outputs, except input button
	MOUSE_PORT = 0xFF ^ MOUSE_RKSBUTTON;	// output ones, but no pullup on button input

	timer_init();							// initialize timer for button monitoring
    sei();									// enable interrupts


	// main loop

	while (true) 
	{
		// === poll Gray encoder inputs and wait for next valid state

		u = GRAY_PIN & 0x0F;				// X0..X3
		if (u == instate[inc (xIn, INMAX)])
		{
			xIn = inc (xIn, INMAX);
			xCount += SCALE;
			xMoved++;
		}
		else if (u == instate[dec (xIn, INMAX)])
		{
			xIn = dec (xIn, INMAX);
			xCount -= SCALE;
			xMoved--;
		}

		u = GRAY_PIN >> 4;					// Y0..Y3
		if (u == instate[inc (yIn, INMAX)])
		{
			yIn = inc (yIn, INMAX);
			yCount += SCALE;
			yMoved++;
		}
		else if (u == instate[dec (yIn, INMAX)])
		{
			yIn = dec (yIn, INMAX);
			yCount -= SCALE;
			yMoved--;
		}


		// === mouse output state follows RKS in single steps

		if (xCount>0) {
			xOut = inc (xOut, OUTMAX);
			xCount --;
		}
		else if (xCount<0) {
			xOut = dec (xOut, OUTMAX);
			xCount ++;
		}

		if (yCount>0) {
			yOut = inc (yOut, OUTMAX);
			yCount --;
		}
		else if (yCount<0) {
			yOut = dec (yOut, OUTMAX);
			yCount ++;
		}

		
		// === Poll RKS button
		//
		// State machine watches wether button is held down for a long time (TIME_PRESS timer ticks),
		// while the mouse is not moving. This is interpreted as a right click.
		//
		// Active button state is 0 (on RKS input and mouse output).

		u = (MOUSE_PIN & MOUSE_RKSBUTTON);

		switch (buttonState) {

			case but_open:					// button currently open
			{
				if (!u) 					// button now pressed
				{
					lButton = 0;			// output left click
					rButton = 1;			// no right click (yet)
					timer = 0;				// reset timer
					xMoved = 0; yMoved = 0;	// no mouse movement yet
					buttonState = but_timer;
				};
				break;
			}

			case but_timer:					// button is held, no movement yet - may become a right-click
			{
				if (u)						// button now released
				{
					lButton = 1;			// release left button
					buttonState = but_open;
				}
				else if (xMoved>MOVE_THRESHOLD || yMoved>MOVE_THRESHOLD 
					 || xMoved<-MOVE_THRESHOLD|| yMoved<-MOVE_THRESHOLD) // mouse movement detected - switch to "dragging" state
				{
					buttonState = but_drag;
				}
				else if (timer>TIME_PRESS)	// time threshold reached - simulate right click
				{
					lButton = 1;			// release left button
					rButton = 0;			// press right button
					buttonState = but_right;
				}
				break;
			}

			case but_drag:					// button is held, with mouse movement - dragging with left button
			case but_right:					// currently simulating right button
			{
				if (u)						// button now released
				{
					lButton = 1;			// release left button
					rButton = 1;			// release right button
					buttonState = but_open;
				}
				break;
			}
		}

/*		// simple implementation without right button emulation
		lButton = (MOUSE_PIN & MOUSE_RKSBUTTON) != 0;
		rButton = 1;
*/

		// === update output: quadrature and button states

		MOUSE_PORT = (outstate[xOut]) + (outstate[yOut]<<2) + (lButton<<4) + (rButton<<5);

	}

}
