/**************************************************************************

	pwm.c

   Z-World, 2002
	This sample program is for the OP7200 series controllers.
	
	This program demonstrates the use of the timer B to generate a PWM
	signal on digital output OUT0. The program generates a 42Hz PWM
	signal with the duty cycle adjustable from 1 to 99%.
	
	Test Instructions:
	------------------
	1. On the controller connect a wire from J3 +PWR to J3 +K. 

	2. Compile and run this program.

	3. Monitor Digitial output OUT0 with a voltmeter.
	  (located on connector J3).

	4. On digital output OUT0 you will see a voltage that computes as
	   follows:

	   Vout = (PWM percentage * +K Voltage)  
	   	
**************************************************************************/
#class auto

int cpldShadow;

#define PWM_PORT0        0x8000			// Digital sinking driver OUT0
#define PWM_PORT1        0x8008			// Digital Sourcing driver OUT0
#define PWM_SHADOW       cpldShadow 
#define PWM_BIT_LOW		~0x01
#define PWM_BIT_HIGH     0x01    	

const float maxcount = 1023;	// Timer B is a 10 bit counter
float dutycycle;					// dutycycle is 1% to 99% of maxcount
unsigned int count;				// dutycycle converted to int
int flag;							// used for ISR program control
int increment, decrement;     // used to control the dutycycle of the PWM signal

char pwm_lsb;
char pwm_msb; 

int key;
void timerb_isr();
			
void update_duty_cycle( void )
{
	count = (int)((dutycycle * maxcount)/100);
	
	// change to interrupt level 3, don't allow interrupts while executing this code 
#asm
	push	ip		;save-off IP state
	ipset	3		;set interrupt priority to level 3

	; Breakdown the 10 bit counter interger into two 8 bit values
	; so that it can be loaded into Timer B when the ISR executes. 
	c pwm_msb = (char) (count >> 2);
	c pwm_lsb = (char) count;
	pop 	ip
#endasm
}


void main()
{
	auto int i;
	
	// initialize the I/O ports
	brdInit();
	digTriStateConfig(0x01);


	// initialize program variables
	dutycycle = 10;
	count = 0;
	increment  = FALSE;
	decrement  = FALSE;

	#if __SEPARATE_INST_DATA__
		interrupt_vector timerb_intvec timerb_isr;
	#else
		SetVectIntern(0x0B, timerb_isr);	   // initialize Timer B interrupt vector
	#endif

	// calculate the default duty cycle setting
	count = (int)((dutycycle * maxcount)/100);
	flag = 0;

	// initialize Timer B
	WrPortI(TBCR, &TBCRShadow, 0x05);	// clock timer B with (Timer A1) and set interrupt level to 1
	WrPortI(TAT1R, &TAT1RShadow, 0xff); // Load Timer A1 with max time constant
	
	WrPortI(TBM1R, NULL, 0 );
	WrPortI(TBL1R, NULL, 0 );				// set initial match

	digOutTriState(0, 1);
	
	// sync-up to Timer B 
	for(i = 0; i < 3; i++)
	{
		while((RdPortI(TBCMR) & 0xC0) != 0x80);
		WrPortI(TBM1R, NULL, 0 );
		WrPortI(TBL1R, NULL, 0 );			
		while((RdPortI(TBCMR) & 0xC0) != 0xC0);
	}
	

	WrPortI(TBCSR, &TBCSRShadow, 0x03);	// enable timer B and B1 match interrupts
	update_duty_cycle();
	
	// display usage message
	printf("PWM control\n\r");
	printf("-----------\n\r");
	printf("- Press I to Increment PWM duty cycle\n\r");
	printf("- Press D to Decrement PWM duty cycle\n\n\r");
	printf ( "Duty Cycle = %d%c \r", (int)dutycycle, '%');

	// generate a PWM signal where the user can change the dutycycle  
	while (1)
	{
		costate		
		{
			// increase dutycycle
			if(increment)
			{
				waitfor(DelayMs(100));
				if (dutycycle < 99) dutycycle++;	
				update_duty_cycle();
				printf ( "Duty Cycle = %d%c \r", (int)dutycycle, '%');
				increment = FALSE;
			
			}
		}
		costate
		{
			// decrease dutycycle  
			if(decrement)
			{
				waitfor(DelayMs(100));
				if (dutycycle > 1 ) dutycycle--;
				update_duty_cycle();
				printf ( "Duty Cycle = %d%c \r", (int)dutycycle, '%');
				decrement = FALSE;
			}	
		}
		costate
		{
			if(kbhit())
			{
				key = getchar();
				if (key == 'I' || key == 'i')		   
				{
					while(kbhit()) getchar();        
					increment = TRUE;      
     			}
     			if (key == 'D' || key == 'd')	       
				{
					while(kbhit()) getchar();        
					decrement = TRUE;      
     			}       
			}
		}
	}

}   /* end of main() */


#asm root nodebug
timerb_isr::
; For this ISR to function properly you MUST update (TBL1R) after (TBM1R)!!!

			push	af						; save registers
			push	hl

	ioi	ld		a, (TBCSR)			; clear the interrupt

			ld		hl, (flag)			; get flag value
			inc	hl						; increment
			ld		(flag), hl			; save for next time

			ld		a, 01h				; mask off all but bit 0
			and 	l						 
			jr		nz, pwm_high 		; jump to set the PWM output high

pwm_low:
			ld    a,(PWM_SHADOW)		; get copy of the shadow register
			res   0,a
		  	ioe	ld (PWM_PORT1), a	; Turn sourcing driver OFF	
			set   0,a
			ioe	ld (PWM_PORT0), a	; Turn sinking driver ON  
			ld    (PWM_SHADOW),a		; update the shadow register
			
			ld		a, (pwm_msb) 		; put high byte value into bits 6 & 7
   		ioi	ld	(TBM1R), a

			ld		a, (pwm_lsb)		; get low byte value
			ioi	ld		(TBL1R), a	; writing to low byte enables next interrupt
			jr		done

pwm_high:
			ld    a, (PWM_SHADOW)	; get copy of the shadow register
			res   0,a
			ioe	ld (PWM_PORT0), a	; Turn sinking driver OFF 
		   set   0,a               
		 	ioe	ld (PWM_PORT1), a	; Turn sourcing driver ON	
			ld    (PWM_SHADOW),a    ; update the shadow register 

			xor	a						; setup to load counter B with zero's
   		ioi	ld	(TBM1R), a
   		ioi	ld	(TBL1R), a		; writing to low byte enables next interrupt
done:
			pop 	hl						; restore registers
			pop	af

			ipres							; restore interrupts
			ret							; return
#endasm
