
// ****************************************************************************
//
//                         Page COM - UART communication
//
// ****************************************************************************

#include "../include.h"

#define COM_DEBUG	0	// debug: 0=normal, 1=check send speed, 2=check receive speed

int COMSpeed = 13;		// COM speed
Bool COMPause = False;		// pause
u32 COMSendLast;		// last send sample
u32 COMSendCnt;			// send counter

#if COM_DEBUG == 2	// debug: 0=normal, 1=check send speed, 2=check receive speed
u32 COMDebCnt;
#endif

#define COM_ROWNUM	6	// number of COM rows (display height is 48 lines, 1 row is 8 lines)
#define COM_BAUDNUM	25

// Nominal Baudrates
const u32 COM_Baud[COM_BAUDNUM] = {
	900,	1200,	1800,	2400,	4800,		// 0..4
	7200,	9600,	14400,	19200,	28800,		// 5..9
	38400,	57600,	76800,	115200,	230400,		// 10..14
	250000,	460800,	500000,	576000,	921600,		// 15..19
	1000000,1200000,1500000,2000000,3000000,	// 20..24
};

const char* const COM_BaudTxt[5] = { "1M", "1.2M", "1.5M", "2M", "3M", };

// Divider setup
const u16 COM_BRR[COM_BAUDNUM] = {
	53333,	40000,	26667,	20000,	10000,
	6667,	5000,	3333,	2500,	1667,
	1250,	833,	625,	417,	208,
	192,	104,	96,	83,	52,
	48,	40,	32,	24,	16,
};

/*
// USART2 interrupt on receive character ... handler located at page_asm.S
// HANDLER void USART2_IRQHandler()
HANDLER void COM_Handler()
{
	// dummy read status register to clear error flags
	(void)USART2->STATR;
	
	// get character
	u8 ch = (u8)USART2->DATAR;

#if COM_DEBUG == 2	// debug: 0=normal, 1=check send speed, 2=check receive speed
	COMDebCnt++;
#endif

	// pointer to screen buffer
	sCOMScreen* b = COMScreen;

	// get current row
	int row = b->row;

	// CR
	if (ch == 13)
	{
		b->len[row] = 0;
	}

	// LF
	else if (ch == 10)
	{
		row = (row + 1) & (COM_SCREENH-1);
		b->row = row;
		b->len[row] = 0;
	}

	// character
	else
	{
		// end of row
		int len = b->len[row];
		if (len >= COM_SCREENW)
		{
			row = (row + 1) & (COM_SCREENH-1);
			b->row = row;
			b->len[row] = 0;
			len = 0;
		}

		// store character
		b->buf[row*COM_SCREENW + len] = ch;
		b->len[row] = len+1;
	}
}
*/

// clear interrupt screen buffer
void COM_Clr()
{
	// disable interrupt
	di();

	// reset current row
	sCOMScreen* b = COMScreen;
	b->row = 0;
	b->len[0] = 0;

	// reset rows - set "not initialized yet"
	b->len32[0] = (u32)-1;
	b->len32[1] = (u32)-1;

	// enable interrupt
	ei();
}

// COM speed setup
void COM_Setup()
{
	USART2_IBaud(COM_BRR[COMSpeed]);
}

// COM initialize
void COM_Init()
{
	// set 'Hold' key to long mode
	KeyHoldLong();

	// clear interrupt screen buffer
	COM_Clr();

	// clear pause
	COMPause = False;

	// setup interrupt priorities to avoid SysTick lock
	NVIC_IRQPriority(IRQ_SYSTICK, IRQ_PRIO_HIGH);		// SysTick
	NVIC_IRQPriority(IRQ_USART2, IRQ_PRIO_NORMAL);		// USART2

	// enable USART2 clock
	USART2_RCCClkEnable();

	// reset USART to defaults
	USART2_Reset();

	// set word length to 8 bits
	USART2_WordLen(USART_WORDLEN_8);

	// set parity to NONE
	USART2_Parity(USART_PARITY_NONE);

	// set 1 stop bit
	USART2_Stop(USART_STOP_1);

	// COM speed setup
	COM_Setup();

	// enable receiver
	USART2_RxEnable();

	// enable transmitter
	USART2_TxEnable();

	// interrupt on received data
	USART2_RxReadyIntEnable();

	// set interrupt handler
	SetHandler(IRQ_USART2, COM_Handler);

	// interrupt enable
	NVIC_IRQEnable(IRQ_USART2);

	// clear flag
	USART2_RxReadyClr();

	// Remap USART2
	// 3 ... PD2:TX, PD3:RX, PA0:CTS, PA1:RTS
	GPIO_Remap_USART2(3);

	// setup pins
	GPIO_Mode(PD3, GPIO_MODE_IN_PU);	// USART2-RX
	GPIO_Mode(PD2, GPIO_MODE_AF);		// USART2-TX

	// enable USART
	USART2_Enable();

	// last send sample
	COMSendLast = Time();

	// reset send counter
	COMSendCnt = 0;
}

// COM terminate
void COM_Term()
{
	// set 'Hold' key to default short mode
	KeyHoldShort();

	// disable interrupt
	USART2_RxReadyIntDisable();
	NVIC_IRQDisable(IRQ_USART2);

	// disable USART
	USART2_Disable();

	// reset USART to defaults
	USART2_Reset();

	// disable USART2 clock
	USART2_RCCClkDisable();

	// setup IRQ priorities to default state
	IrqPriorDef();

	// reset pins
	GPIO_PinReset(PD3);
	GPIO_PinReset(PD2);
}

// display title (does not update display)
void COM_DispTitle()
{
	// select font 8x12
	SelFont12();

	// clear title
	DrawRectClr(PageX, ROW0_Y, WIDTH-1-PageX, FONTH);

	// pause
	if (COMPause)
	{
		DrawText("HOLD", (WIDTH-1-PageX-4*8)/2+PageX, ROW0_Y);
	}
	else
	{
		// display baud rate
		const char* s = DecNumBuf;
		int inx = COMSpeed;
		int len;
		if (inx < 20)
			len = DecUNum(DecNumBuf, COM_Baud[inx], '\'');
		else
		{
			inx -= 20;
			s = COM_BaudTxt[inx];
			len = StrLen(s);
		}
		int x = (WIDTH-1-PageX-len*8-2*8)/2 + PageX;
		DrawText(s, x, ROW0_Y);
		x += len*8;
		DrawText("Bd", x, ROW0_Y);
	}
}

// COM send update
void COM_Update()
{
	// send sample "Test123 "
	if ((u32)(Time() - COMSendLast) >= 1000000*HCLK_PER_US)
	{
#if COM_DEBUG > 0	// debug: 0=normal, 1=check send speed, 2=check receive speed
		// send samples
		static char ch = 'A';
		//di();
		cb(); u32 t1 = Time(); cb();
#if COM_DEBUG == 2	// debug: 0=normal, 1=check send speed, 2=check receive speed
		COMDebCnt = 0;
#endif
		int i = 0;
		while (((u32)Time() - t1) < 200000*HCLK_PER_US)
		{
			USART2_SendChar(ch);
			i++;
		}
		while (!USART2_TxSent()) {}
		cb(); u32 t2 = Time(); cb();
		//ei();
		ch++;
		if (ch == 'Z'+1) ch = 'A';

		// display measured baud rate
#if COM_DEBUG == 2	// debug: 0=normal, 1=check send speed, 2=check receive speed
		WaitMs(20);
		u32 d = (u64)COMDebCnt*10*1000000*HCLK_PER_US / (t2 - t1);
#else
		u32 d = (u64)i*10*1000000*HCLK_PER_US / (t2 - t1);
#endif
		SelFont12();
		DrawRectClr(PageX, ROW0_Y, WIDTH-1-PageX, FONTH);
		i = DecUNum(DecNumBuf, d, '\'');
		DrawText(DecNumBuf, (WIDTH-1-PageX-i*8)/2+PageX, ROW0_Y);
		DispUpdate();
		WaitMs(300);
		COM_DispTitle();
		DispUpdate();
		WaitMs(300);
#else
		// send test pattern
		USART2_SendBuf("Test", 4);
		int len = DecUNum(DecNumBuf, COMSendCnt, 0);
		USART2_SendBuf(DecNumBuf, len);
		USART2_SendChar(' ');
		COMSendCnt++;	// send counter
		COMSendLast = Time(); // send last time
#endif
	}
}

// COM display
void COM_Disp()
{
	int i, j, x, row, row2;
	char* s;

	// screen snapshot
	memcpy(COMScreen2, COMScreen, sizeof(sCOMScreen));

	// pointer to screen buffer
	sCOMScreen* b = COMScreen2;

	// find first valid line
	row = b->row;
	for (i = COM_ROWNUM-1; i > 0; i--)
	{
		// previous row
		row2 = (row - 1) & (COM_SCREENH-1);

		// previous row is not valid
		if (b->len[row2] == 0xff) break;

		// previous row is valid
		row = row2;
	}

	// clear screen
	DrawRectClrFast(0, TITLE_H, WIDTH, HEIGHT-TITLE_H);

	// select font 8x8
	SelFont8();

	// draw rows
	for (i = 0; i < COM_ROWNUM; i++)
	{
		// pointer to row data
		s = &b->buf[row*COM_SCREENW];

		// draw one row
		x = 0;
		j = b->len[row];
		if (j == 0xff) j = 0;
		for (; j > 0; j--)
		{
			DrawCharFast(*s++, x, i*8 + TITLE_H);
			x += 8;
		}

		// shift row
		row = (row + 1) & (COM_SCREENH-1);
	}

	// display update
	DispUpdate();
}

// Page COM (returns key PREV/NEXT)
u8 PageCOM()
{
	int i;
	u32 t;
	u8 key;

	// COM initialize
	COM_Init();

	// display title
	COM_DispTitle();

	// COM display, display update
	COM_Disp();

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

		// COM send update
		COM_Update();

		// COM display
		if (!COMPause) COM_Disp();

		// keyboard input
		key = KeyGet();
		switch (key)
		{
		// change page
		case KEY_PREV:
		case KEY_NEXT:
			// COM terminate
			COM_Term();
			return key;

		// FAST - higher baudrate
		case KEY_FAST:
			COMPause = False;
			i = COMSpeed + 1;
			if (i >= COM_BAUDNUM) i = 0;
			COMSpeed = i;
			COM_Setup(); // COM speed setup
			COM_DispTitle();
			DispUpdate();
			break;
			
		// SLOW - lower baudrate
		case KEY_SLOW:
			COMPause = False;
			i = COMSpeed - 1;
			if (i < 0) i = COM_BAUDNUM - 1;
			COMSpeed = i;
			COM_Setup(); // COM speed setup
			COM_DispTitle();
			DispUpdate();
			break;

		// Hold
		case KEY_HOLD:
			COMPause = !COMPause;
			COM_DispTitle();
			DispUpdate();
			break;

		// Hold long - reset
		case KEY_HOLD_LONG:
			COM_Clr();		// clear interrupt screen buffer
			COMSendLast = Time();	// last send time
			COMSendCnt = 0;		// reset send counter
			break;
		}
	}
}
