
// ****************************************************************************
//
//                         Page PWM - function generator
//
// ****************************************************************************

#include "../include.h"

u32 PwmSampleRate;		// current PWM sample rate * 16
u32 PwmPhaseInc;		// PWM phase increment
u32 PwmPhaseAcc;		// PWN phase accumulator
int PwmSet = PWMSET_TONE_SIN;	// current PWM tone set
u8 PwmShape = PWMSHAPE_SIN;	// current PWM signal shape
u32 PwmFreq;			// current PWM frequency ^ exponent (prepared for DispUVal)
int PwmEx;			// current PWM frequency exponent
int PwmNext;			// PWM next sample

// current tone
u8 PwmTone[PWMSET_NUM] = {
	TONE_DEF,	// PWMSET_TONE_SIN	// decimal tones, sine
	TONE_DEF,	// PWMSET_TONE_TRI	// decimal tones, triangle
	TONE_DEF,	// PWMSET_TONE_SAW	// decimal tones, saw
	NOTE_DEF,	// PWMSET_NOTE_SIN	// notes, sine
	NOTE_DEF,	// PWMSET_NOTE_TRI	// notes, triangle
	NOTE_DEF,	// PWMSET_NOTE_SAW	// notes, saw
};

// max. number of tones in this tone set
const u8 PwmToneMax[PWMSET_NUM] = {
	TONE_NUM_PWM,	// PWMSET_TONE_SIN	// decimal tones, sine
	TONE_NUM_PWM,	// PWMSET_TONE_TRI	// decimal tones, triangle
	TONE_NUM_PWM,	// PWMSET_TONE_SAW	// decimal tones, saw
	NOTE_NUM,	// PWMSET_NOTE_SIN	// notes, sine
	NOTE_NUM,	// PWMSET_NOTE_TRI	// notes, triangle
	NOTE_NUM,	// PWMSET_NOTE_SAW	// notes, saw
};

// PWM signal shape
const u8 PwmShapeTab[PWMSET_NUM] = {
	PWMSHAPE_SIN,	// PWMSET_TONE_SIN	// decimal tones, sine
	PWMSHAPE_TRI,	// PWMSET_TONE_TRI	// decimal tones, triangle
	PWMSHAPE_SAW,	// PWMSET_TONE_SAW	// decimal tones, saw
	PWMSHAPE_SIN,	// PWMSET_NOTE_SIN	// notes, sine
	PWMSHAPE_TRI,	// PWMSET_NOTE_TRI	// notes, triangle
	PWMSHAPE_SAW,	// PWMSET_NOTE_SAW	// notes, saw
};

// PWM title
const char* const PwmTitle[PWMSET_NUM] = {
	"Freq sine",	// PWMSET_TONE_SIN	// decimal tones, sine
	"Freq tri",	// PWMSET_TONE_TRI	// decimal tones, triangle
	"Freq saw",	// PWMSET_TONE_SAW	// decimal tones, saw
	"Note sine",	// PWMSET_NOTE_SIN	// notes, sine
	"Note tri",	// PWMSET_NOTE_TRI	// notes, triangle
	"Note saw",	// PWMSET_NOTE_SAW	// notes, saw
};

// sine table (1st quadrant of the sine, values in range 0..65535)
const u16 PWM_SinTab[128] = {
	0,	811,	1621,	2431,	3241,	4050,	4859,	5667,
	6474,	7280,	8085,	8889,	9691,	10492,	11291,	12089,
	12885,	13678,	14470,	15260,	16047,	16831,	17613,	18393,
	19169,	19943,	20714,	21481,	22245,	23006,	23763,	24517,
	25266,	26012,	26754,	27492,	28226,	28955,	29680,	30401,
	31116,	31827,	32533,	33235,	33931,	34622,	35307,	35987,
	36662,	37331,	37994,	38652,	39303,	39949,	40589,	41222,
	41849,	42469,	43083,	43691,	44292,	44886,	45473,	46053,
	46626,	47192,	47751,	48303,	48847,	49383,	49912,	50434,
	50948,	51454,	51952,	52442,	52924,	53398,	53864,	54321,
	54771,	55212,	55644,	56068,	56483,	56890,	57288,	57677,
	58058,	58429,	58792,	59146,	59490,	59826,	60152,	60469,
	60777,	61076,	61365,	61645,	61915,	62176,	62427,	62669,
	62902,	63124,	63337,	63541,	63734,	63918,	64092,	64256,
	64411,	64555,	64690,	64815,	64930,	65035,	65130,	65215,
	65290,	65355,	65410,	65455,	65490,	65515,	65530,	65535,
};

/*
// PWM Timer 2 interrupt handler - Sine ... handler located at page_asm.S
//HANDLER void TIM2_IRQHandler()
HANDLER void PWM_Handler_Sine()
{
	// set next sample
	TIM2_Comp1(PwmNext);
	cb();

	// clear interrupt flag
	TIM2_UpIntClr();

	// shift phase accumulator
	u32 acc = PwmPhaseAcc + PwmPhaseInc;
	PwmPhaseAcc = acc;

	// sample phase
	int phase = acc >> (32 - 9);	// phase range 0..511

	// get sample 1 from sine table (samples are in range -65535..+65535)
	int quadrant = phase >> 7;	// sine quadrant 0...3
	if ((quadrant & 1) != 0) phase = 127-phase; // quadrant pi/2..pi or pi*3/2..2*pi
	int s = PWM_SinTab[phase & 0x7f];
	if ((quadrant & 2) != 0) s = -s; // quadrant pi..pi*3/2 or pi*3/2..2*pi

	// convert range -65535..+65535 to range 0..LOOP-1
	s += 65535;			// range is now 0..131070
	s = (s * PWM_LOOP) >> 17; 	// convert to range 0..LOOP-1

	// save next sample
	PwmNext = s;
}
*/

/*
// PWM Timer 2 interrupt handler - Triangle ... handler located at page_asm.S
HANDLER void PWM_Handler_Tri()
{
	// set next sample (fast setup is needed)
	TIM2_Comp1(PwmNext);
	cb();

	// clear interrupt flag
	TIM2_UpIntClr();

	// shift phase accumulator
	u32 acc = PwmPhaseAcc + PwmPhaseInc;
	PwmPhaseAcc = acc;

	// sample phase, get 16 bits
	int phase = (u32)acc >> 16;		// value 0..65535
	if ((s32)acc < 0) phase = 65535 - phase; // value 

	// convert range 0..32767 to range 0..LOOP-1
	int s = (phase * PWM_LOOP) >> 15;

	// save next sample
	PwmNext = s;
}
*/

/*
// PWM Timer 2 interrupt handler - Saw ... handler located at page_asm.S
HANDLER void PWM_Handler_Saw()
{
	// set next sample (fast setup is needed)
	TIM2_Comp1(PwmNext);
	cb();

	// clear interrupt flag
	TIM2_UpIntClr();

	// shift phase accumulator
	u32 acc = PwmPhaseAcc + PwmPhaseInc;
	PwmPhaseAcc = acc;

	// sample phase, get 16 bits
	int phase = acc >> 16;		// value 0..65535

	// convert range 0..65535 to range 0..LOOP-1
	int s = (phase * PWM_LOOP) >> 16;

	// save next sample
	PwmNext = s;
}
*/

// PWM setup
void PWM_Setup()
{
	// get current tone set
	int set = PwmSet;

	// get sample shape
	PwmShape = PwmShapeTab[set];

	// get current tone
	int tone = PwmTone[set];

	// get frequency * 65536
	s64 freq;
	if (PwmToneMax[set] == NOTE_NUM) // notes
		freq = NoteFreq[tone];
	else
		freq = (s64)ToneFreq[tone] << 16;

	// get phase increment
// Required frequency in Hz: ftarget = freq/65536
// PWM sample rate in Hz: fs = PwmSampleRate/16
// phase increment: phase_inc = ftarget/fs * 2^32 = freq/(2^16) / (PwmSampleRate/(2^4)) * 2^32 =
//   = freq / PwmSampleRate * 2^20
	PwmPhaseInc = (freq << 20) / PwmSampleRate;

	// prepare frequency for DispUVal
	freq *= 10000;	// max. value = 48000000*65536*10000 = 31457280000000000 = 0x006FC23AC0000000, s64 will not overflow
	freq >>= 16;
	int eq = -4;
	while (freq > 0x7fffffff)
	{
		freq /= 10;
		eq++;
	}
	PwmFreq = (u32)freq;
	PwmEx = eq;
	PwmNext = PWM_LOOP/2;	// PWM next sample
}

// PWM output initialize
void PWM_Init()
{
	// prepare current PWM sample rate * 16
	PwmSampleRate = ((SystemCoreClock << 4) + PWM_LOOP/2) / PWM_LOOP;

	// Enable timer clock source
	TIM2_ClkEnable();

	// Reset timer to default setup
	TIM2_Reset();

	// select input from internal clock CK_INT
	TIM2_InMode(TIM_INMODE_INT);

	// enable compare output
	TIM2_CC1Enable();

	// setup prescaler
	TIM2_Presc(0);
	TIM2_Load(PWM_LOOP-1);
	TIM2_Comp1(PWM_LOOP >> 1);

	// direction up
	TIM2_DirUp();

	// enable auto-reload of preload compare register
	TIM2_AutoReloadEnable();

	// setup compare
	TIM2_OC1Mode(TIM_COMP_PWM1);	// set compare mode
	TIM2_OC1PreEnable();		// enable preload compare register

	// enable main output
	TIM2_OCEnable();

	// reload immediately
	TIM2_Update();

	// remap Timer 2
	// Output: PC5, Timer 2 channel 1 mapping 2
	//	2 ... >>> PC5:CH1/ETR <<<, PC2:CH2, PD2:CH3/CH1N, PC1:CH4/CH2N
	GPIO_Remap_TIM2(2);

	// PWM setup
	PWM_Setup();

	// setup GPIO pin
	GPIO_Mode(PWM_GPIO, GPIO_MODE_AF);

	// set PWM Timer 2 interrupt handler
	if (PwmShape == PWMSHAPE_SIN)
		SetHandler(IRQ_TIM2, PWM_Handler_Sine);
	else if (PwmShape == PWMSHAPE_TRI)
		SetHandler(IRQ_TIM2, PWM_Handler_Tri);
	else
		SetHandler(IRQ_TIM2, PWM_Handler_Saw);

	// enable update interrupt
	TIM2_UpIntEnable();

	// interrupt enable, handler TIM2_IRQHandler()
	NVIC_IRQEnable(IRQ_TIM2);
	TIM2_UpIntClr();

	// enable timer
	TIM2_Enable();
}

// PWM output terminate
void PWM_Term()
{
	// interrupt disable
	NVIC_IRQDisable(IRQ_TIM2);
	TIM2_UpIntDisable();
	TIM2_UpIntClr();

	// reset GPIO pin
	GPIO_PinReset(PWM_GPIO);

	// reset Timer 2
	TIM2_Reset();

	// Timer 2 clock disable
	RCC_TIM2ClkDisable();
}

// display PWM state
void PWM_Disp()
{
	int tone, set;

	// set font 8x12
	SelFont12();

	// get current tone set
	set = PwmSet;

	// display title
	DrawRectClr(PageX, ROW0_Y, WIDTH-1-PageX, FONTH);
	const char* t = PwmTitle[set];
	DrawText(t, (WIDTH-PageX-StrLen(t)*8)/2 + PageX, ROW0_Y);

	// get tone
	tone = PwmTone[set];

	// display GEN tone - used also with PWM
	GEN_DispTone((PwmToneMax[set] == NOTE_NUM) ? GENSET_NOTE : GENSET_TONE, tone);

	// display frequency
	// display unsigned value (returns next X coordinate; need to call DispUpdate())
	//  x ... X coordinate
	//  y ... Y coordinate
	//  val ... value
	//  dig ... number of valid digits
	//  ex ... decimal exponent related to base unit, in range -18..+8
	//  unit ... unit character, 0=none
	//  small ... use small unit characters
	int x = DispUVal(0, ROW3_Y, PwmFreq, 6, PwmEx, 'H', True, False);
	DrawChar('z', x, ROW3_Y+SMALL_Y);

	// display update
	DispUpdate();
}

// Page PWM (returns key PREV/NEXT)
u8 PagePWM()
{
	u8 key;
	int i, set;

	// PWM initialize
	PWM_Init();	// PWM output initialize (Timer 2)

	// display PWM state
	PWM_Disp();

	while (True)
	{
		// reload watchdog counter
		IWDG_Reload();

		// get key
		key = KeyGet();
		switch (key)
		{
		// Prev
		case KEY_PREV:
		// Next
		case KEY_NEXT:
			// PWM terminate
			PWM_Term();
			return key;

		// Slow
		case KEY_SLOW:
			set = PwmSet;
			i = PwmTone[set] - 1;
			if (i < 0) i = PwmToneMax[set] - 1;
			PwmTone[set] = i;
			PWM_Setup();
			PWM_Disp();
			break;		

		// Fast
		case KEY_FAST:
			set = PwmSet;
			i = PwmTone[set] + 1;
			if (i >= PwmToneMax[set]) i = 0;
			PwmTone[set] = i;
			PWM_Setup();
			PWM_Disp();
			break;

		// Hold
		case KEY_HOLD:
			PWM_Term();
			i = PwmSet + 1;
			if (i >= PWMSET_NUM) i = 0;
			PwmSet = i;
			PWM_Init();
			PWM_Disp();
			break;
		}
	}
}
