#include "lcd.h"
#include "lcd_font.h"


/**************lcd_gpio初始化*****************/
#if HARDWARE_SPI   //切换硬件和软件SPI
void LCD_GPIO_Init(void)
{
	gpio_init_typedef gpio_init_structure;

	//SPI1初始化
    spi1_init(SPI_2WIRE_MODE,HARDWARE_SPI_BAUD_RATE);

	gpio_init_structure.gpio_pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 | GPIO_PIN_3;//RES(pin0)/DC(pin1)/CS(pin2)/BL(pin3)
 	gpio_init_structure.gpio_dir = GPIO_DIR_OUTPUT;
	gpio_init_structure.gpio_fen = GPIO_FEN_GPIO;
	gpio_init_structure.gpio_drv = GPIO_DRV_8MA;
    gpio_init_structure.gpio_mode = GPIO_MODE_DIGITAL;

 	gpio_init(GPIOF_REG, &gpio_init_structure);
    gpio_set_bits(GPIOF_REG, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 | GPIO_PIN_3);
}
/******************************************************************************
      函数说明：LCD串行数据写入函数
      入口数据：dat  要写入的串行数据
      返回值：  无
******************************************************************************/
void LCD_Writ_Bus(u8 data)
{
	LCD_CS_Clr();
    spi_dir_sel(SPI1, SPI_DIR_TX);
    spi_send_data(SPI1, data);
    while (!spi_get_flag(SPI1, SPI_FLAG_PENDING));                   //Wait pending
	LCD_CS_Set();
}


extern const u32 sys_clk_geehy[8];
void spi1_init(u8 mode,u32 baud)
{
    u32 sysclk;
    gpio_init_typedef gpio_init_structure;
    spi_init_typedef spi_init_structure;

    clock_gate1_cmd(CLOCK_GATE1_SPI1, ENABLE);

    if(mode==SPI_2WIRE_MODE)
    {
        gpio_init_structure.gpio_pin = GPIO_PIN_4;          //CLK
        gpio_init_structure.gpio_dir = GPIO_DIR_OUTPUT;
        gpio_init_structure.gpio_fen = GPIO_FEN_PER;
        gpio_init_structure.gpio_mode = GPIO_MODE_DIGITAL;
        gpio_init_structure.gpio_pupd = GPIO_PUPD_UP;
        gpio_init(GPIOF_REG, &gpio_init_structure);
        gpio_init_structure.gpio_pin = GPIO_PIN_5;          //DIO
        gpio_init_structure.gpio_dir = GPIO_DIR_INPUT;
        gpio_init(GPIOF_REG, &gpio_init_structure);
        gpio_func_mapping(SPI1_MAP_G5);
    }

    sysclk = get_cur_sysclk();
//    printf("sysclk:%d",sysclk);
    spi_init_structure.baud = sys_clk_geehy[sysclk]*1000000/baud - 1;
    spi_init_structure.bus_mode = mode;
    spi_init_structure.clk_mode = SPI_CLK_HIGH;     //时钟空闲时为高
    spi_init_structure.spi_oss = 0;     //SPI sample data is at the same clock edge with output data.0:difference,1:same
    spi_init_structure.spi_sm = 0;      //Slave mode select bit.0: master mode
    spi_init_structure.spi_smps = 0;    //SPI output edge select bit.0: output data at the falling edge;
    spi_init(SPI1, &spi_init_structure);
    spi_cmd(SPI1, ENABLE);
}

#else  //软件模拟SPI

void LCD_GPIO_Init(void)
{
	gpio_init_typedef gpio_init_structure;

	gpio_init_structure.gpio_pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 | GPIO_PIN_3;//RES(pin0)/DC(pin1)/CS(pin2)/BL(pin3)
 	gpio_init_structure.gpio_dir = GPIO_DIR_OUTPUT;
	gpio_init_structure.gpio_fen = GPIO_FEN_GPIO;
	gpio_init_structure.gpio_drv = GPIO_DRV_8MA;
    gpio_init_structure.gpio_mode = GPIO_MODE_DIGITAL;

 	gpio_init(GPIOF_REG, &gpio_init_structure);
    gpio_set_bits(GPIOF_REG, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 | GPIO_PIN_3);

	gpio_init_structure.gpio_pin = GPIO_PIN_4|GPIO_PIN_5;  //CLK==SCL(pin3)   SDA=DO(MOSI,pin4)
 	gpio_init(GPIOF_REG, &gpio_init_structure);
    gpio_set_bits(GPIOF_REG, GPIO_PIN_4|GPIO_PIN_5);

}

/******************************************************************************
      函数说明：LCD串行数据写入函数
      入口数据：dat  要写入的串行数据
      返回值：  无
******************************************************************************/
void LCD_Writ_Bus(u8 dat)
{
	u8 i;
	LCD_CS_Clr();
	for(i=0;i<8;i++)
	{
		LCD_SCLK_Clr();
		if(dat&0x80)
		{
		   LCD_MOSI_Set();
		}
		else
		{
		   LCD_MOSI_Clr();
		}
		LCD_SCLK_Set();
		dat<<=1;
	}
  LCD_CS_Set();
}

#endif
/************************************************/

/****************LCD 驱动底层函数****************/

/******************************************************************************
      函数说明：LCD写入数据
      入口数据：dat 写入的数据
      返回值：  无
******************************************************************************/
void LCD_WR_DATA8(u8 dat)
{
	LCD_Writ_Bus(dat);
}

/******************************************************************************
      函数说明：LCD写入数据
      入口数据：dat 写入的数据
      返回值：  无
******************************************************************************/
void LCD_WR_DATA(u16 dat)
{
	LCD_Writ_Bus(dat>>8);
	LCD_Writ_Bus(dat);
}

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


/******************************************************************************
      函数说明：LCD写入命令
      入口数据：dat 写入的命令
      返回值：  无
******************************************************************************/
void LCD_WR_REG(u8 dat)
{
	LCD_DC_Clr();//写命令
	LCD_Writ_Bus(dat);
	LCD_DC_Set();//写数据
}


/******************************************************************************
      函数说明：设置起始和结束地址
      入口数据：x1,x2 设置列的起始和结束地址
                y1,y2 设置行的起始和结束地址
      返回值：  无
******************************************************************************/
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
	if(USE_HORIZONTAL==0)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1);
		LCD_WR_DATA(x2);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1);
		LCD_WR_DATA(y2);
		LCD_WR_REG(0x2c);//储存器写
	}
	else if(USE_HORIZONTAL==1)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1);
		LCD_WR_DATA(x2);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1+80);
		LCD_WR_DATA(y2+80);
		LCD_WR_REG(0x2c);//储存器写
	}
	else if(USE_HORIZONTAL==2)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1);
		LCD_WR_DATA(x2);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1);
		LCD_WR_DATA(y2);
		LCD_WR_REG(0x2c);//储存器写
	}
	else
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1+80);
		LCD_WR_DATA(x2+80);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1);
		LCD_WR_DATA(y2);
		LCD_WR_REG(0x2c);//储存器写
	}
}

/**
 * LCD初始化
 */
void LCD_Init(void)
{
    LCD_GPIO_Init();
	LCD_RES_Clr();//复位
	delay_ms(100);
	LCD_RES_Set();
	delay_ms(100);

	LCD_BLK_Set();//打开背光
  delay_ms(100);

	//************* Start Initial Sequence **********//
	LCD_WR_REG(0x11); //Sleep out
	delay_ms(120);              //Delay 120ms
	//************* Start Initial Sequence **********//
	LCD_WR_REG(0x36);
	if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
	else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
	else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
	else LCD_WR_DATA8(0xA0);

	LCD_WR_REG(0x3A);
	LCD_WR_DATA8(0x05);

	LCD_WR_REG(0xB2);
	LCD_WR_DATA8(0x0C);
	LCD_WR_DATA8(0x0C);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x33);
	LCD_WR_DATA8(0x33);

	LCD_WR_REG(0xB7);
	LCD_WR_DATA8(0x35);

	LCD_WR_REG(0xBB);
	LCD_WR_DATA8(0x32); //Vcom=1.35V

	LCD_WR_REG(0xC2);
	LCD_WR_DATA8(0x01);

	LCD_WR_REG(0xC3);
	LCD_WR_DATA8(0x15); //GVDD=4.8V  颜色深度

	LCD_WR_REG(0xC4);
	LCD_WR_DATA8(0x20); //VDV, 0x20:0v

	LCD_WR_REG(0xC6);
	LCD_WR_DATA8(0x0F); //0x0F:60Hz

	LCD_WR_REG(0xD0);
	LCD_WR_DATA8(0xA4);
	LCD_WR_DATA8(0xA1);

	LCD_WR_REG(0xE0);
	LCD_WR_DATA8(0xD0);
	LCD_WR_DATA8(0x08);
	LCD_WR_DATA8(0x0E);
	LCD_WR_DATA8(0x09);
	LCD_WR_DATA8(0x09);
	LCD_WR_DATA8(0x05);
	LCD_WR_DATA8(0x31);
	LCD_WR_DATA8(0x33);
	LCD_WR_DATA8(0x48);
	LCD_WR_DATA8(0x17);
	LCD_WR_DATA8(0x14);
	LCD_WR_DATA8(0x15);
	LCD_WR_DATA8(0x31);
	LCD_WR_DATA8(0x34);

	LCD_WR_REG(0xE1);
	LCD_WR_DATA8(0xD0);
	LCD_WR_DATA8(0x08);
	LCD_WR_DATA8(0x0E);
	LCD_WR_DATA8(0x09);
	LCD_WR_DATA8(0x09);
	LCD_WR_DATA8(0x15);
	LCD_WR_DATA8(0x31);
	LCD_WR_DATA8(0x33);
	LCD_WR_DATA8(0x48);
	LCD_WR_DATA8(0x17);
	LCD_WR_DATA8(0x14);
	LCD_WR_DATA8(0x15);
	LCD_WR_DATA8(0x31);
	LCD_WR_DATA8(0x34);
	LCD_WR_REG(0x21);

	LCD_WR_REG(0x29);
}

/*****************LCD 应用层代码****************/

/******************************************************************************
      函数说明：在指定区域填充颜色
      入口数据：xsta,ysta   起始坐标
                xend,yend   终止坐标
                color       要填充的颜色
      返回值：  无
******************************************************************************/
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color)
{
	u16 i,j;
	LCD_Address_Set(xsta,ysta,xend-1,yend-1);//设置显示范围
	for(i=ysta;i<yend;i++)
	{
		for(j=xsta;j<xend;j++)
		{
			LCD_WR_DATA(color);
		}
	}
}

/**
  * 函    数：LCD 在指定位置画一个点
  * 参    数：X 指定点的横坐标
  * 参    数：Y 指定点的纵坐标
  * 返 回 值：无
  */
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
	LCD_Address_Set(x,y,x,y);//设置光标位置
	LCD_WR_DATA(color);
}

/**
  * 函    数：LCD画线
  * 参    数：X0 指定一个端点的横坐标
  * 参    数：Y0 指定一个端点的纵坐标
  * 参    数：X1 指定另一个端点的横坐标
  * 参    数：Y1 指定另一个端点的纵坐标
  * 返 回 值：无
  */
void LCD_DrawLine(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint16_t color)
{
	int16_t x, y, dx, dy, d, incrE, incrNE, temp;
	int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
	uint8_t yflag = 0, xyflag = 0;

	if (y0 == y1)		//横线单独处理
	{
		/*0号点X坐标大于1号点X坐标，则交换两点X坐标*/
		if (x0 > x1) {temp = x0; x0 = x1; x1 = temp;}

		/*遍历X坐标*/
		for (x = x0; x <= x1; x ++)
		{
			LCD_DrawPoint(x, y0, color);	//依次画点
		}
	}
	else if (x0 == x1)	//竖线单独处理
	{
		/*0号点Y坐标大于1号点Y坐标，则交换两点Y坐标*/
		if (y0 > y1) {temp = y0; y0 = y1; y1 = temp;}

		/*遍历Y坐标*/
		for (y = y0; y <= y1; y ++)
		{
			LCD_DrawPoint(x0, y, color);	//依次画点
		}
	}
	else				//斜线
	{
		/*使用Bresenham算法画直线，可以避免耗时的浮点运算，效率更高*/
		/*参考文档：https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
		/*参考教程：https://www.bilibili.com/video/BV1364y1d7Lo*/

		if (x0 > x1)	//0号点X坐标大于1号点X坐标
		{
			/*交换两点坐标*/
			/*交换后不影响画线，但是画线方向由第一、二、三、四象限变为第一、四象限*/
			temp = x0; x0 = x1; x1 = temp;
			temp = y0; y0 = y1; y1 = temp;
		}

		if (y0 > y1)	//0号点Y坐标大于1号点Y坐标
		{
			/*将Y坐标取负*/
			/*取负后影响画线，但是画线方向由第一、四象限变为第一象限*/
			y0 = -y0;
			y1 = -y1;

			/*置标志位yflag，记住当前变换，在后续实际画线时，再将坐标换回来*/
			yflag = 1;
		}

		if (y1 - y0 > x1 - x0)	//画线斜率大于1
		{
			/*将X坐标与Y坐标互换*/
			/*互换后影响画线，但是画线方向由第一象限0~90度范围变为第一象限0~45度范围*/
			temp = x0; x0 = y0; y0 = temp;
			temp = x1; x1 = y1; y1 = temp;

			/*置标志位xyflag，记住当前变换，在后续实际画线时，再将坐标换回来*/
			xyflag = 1;
		}

		/*以下为Bresenham算法画直线*/
		/*算法要求，画线方向必须为第一象限0~45度范围*/
		dx = x1 - x0;
		dy = y1 - y0;
		incrE = 2 * dy;
		incrNE = 2 * (dy - dx);
		d = 2 * dy - dx;
		x = x0;
		y = y0;

		/*画起始点，同时判断标志位，将坐标换回来*/
		if (yflag && xyflag){LCD_DrawPoint(y, -x, color);}
		else if (yflag)		{LCD_DrawPoint(x, -y, color);}
		else if (xyflag)	{LCD_DrawPoint(y, x, color);}
		else				{LCD_DrawPoint(x, y, color);}

		while (x < x1)		//遍历X轴的每个点
		{
			x ++;
			if (d < 0)		//下一个点在当前点东方
			{
				d += incrE;
			}
			else			//下一个点在当前点东北方
			{
				y ++;
				d += incrNE;
			}

			/*画每一个点，同时判断标志位，将坐标换回来*/
			if (yflag && xyflag){LCD_DrawPoint(y, -x, color);}
			else if (yflag)		{LCD_DrawPoint(x, -y, color);}
			else if (xyflag)	{LCD_DrawPoint(y, x, color);}
			else				{LCD_DrawPoint(x, y, color);}
		}
	}
}

/**
  * 函    数：LCD 矩形
  * 参    数：X 指定矩形左上角的横坐标
  * 参    数：Y 指定矩形左上角的纵坐标
  * 参    数：Width 指定矩形的宽度
  * 参    数：Height 指定矩形的高度
  * 参    数：IsFilled 指定矩形是否填充
  *           范围：LCD_UNFILLED		不填充
  *                 LCD_FILLED			填充
  * 返 回 值：无
  */
void LCD_DrawRectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height ,uint16_t color, uint8_t IsFilled)
{
	uint8_t i, j;
	if (!IsFilled)		//指定矩形不填充
	{
		/*遍历上下X坐标，画矩形上下两条线*/
		for (i = X; i < X + Width; i ++)
		{
			LCD_DrawPoint(i, Y,color);
			LCD_DrawPoint(i, Y + Height - 1,color);
		}
		/*遍历左右Y坐标，画矩形左右两条线*/
		for (i = Y; i < Y + Height; i ++)
		{
			LCD_DrawPoint(X, i,color);
			LCD_DrawPoint(X + Width - 1, i,color);
		}
	}
	else				//指定矩形填充
	{
		/*遍历X坐标*/
		for (i = X; i < X + Width; i ++)
		{
			/*遍历Y坐标*/
			for (j = Y; j < Y + Height; j ++)
			{
				/*在指定区域画点，填充满矩形*/
				LCD_DrawPoint(i, j,color);
			}
		}
	}
}

/**
  * 函    数：判断指定点是否在指定多边形内部
  * 参    数：nvert 多边形的顶点数
  * 参    数：vertx verty 包含多边形顶点的x和y坐标的数组
  * 参    数：testx testy 测试点的X和y坐标
  * 返 回 值：指定点是否在指定多边形内部，1：在内部，0：不在内部
  */
uint8_t LCD_pnpoly(uint8_t nvert, int16_t *vertx, int16_t *verty, int16_t testx, int16_t testy)
{
	int16_t i, j, c = 0;

	/*此算法由W. Randolph Franklin提出*/
	/*参考链接：https://wrfranklin.org/Research/Short_Notes/pnpoly.html*/
	for (i = 0, j = nvert - 1; i < nvert; j = i++)
	{
		if (((verty[i] > testy) != (verty[j] > testy)) &&
			(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
		{
			c = !c;
		}
	}
	return c;
}

/**
  * 函    数：LCD 三角形
  * 参    数：X0 指定第一个端点的横坐标
  * 参    数：Y0 指定第一个端点的纵坐标
  * 参    数：X1 指定第二个端点的横坐标
  * 参    数：Y1 指定第二个端点的纵坐标
  * 参    数：X2 指定第三个端点的横坐标
  * 参    数：Y2 指定第三个端点的纵坐标，
  * 参    数：IsFilled 指定三角形是否填充
  *           范围：LCD_UNFILLED		不填充
  *                 LCD_FILLED			填充
  * 返 回 值：无
  */
void LCD_DrawTriangle(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t X2, uint8_t Y2, uint16_t color, uint8_t IsFilled)
{
	uint8_t minx = X0, miny = Y0, maxx = X0, maxy = Y0;
	uint8_t i, j;
	int16_t vx[] = {X0, X1, X2};
	int16_t vy[] = {Y0, Y1, Y2};

	if (!IsFilled)			//指定三角形不填充
	{
		/*调用画线函数，将三个点用直线连接*/
		LCD_DrawLine(X0, Y0, X1, Y1 ,color);
		LCD_DrawLine(X0, Y0, X2, Y2 ,color);
		LCD_DrawLine(X1, Y1, X2, Y2 ,color);
	}
	else					//指定三角形填充
	{
		/*找到三个点最小的X、Y坐标*/
		if (X1 < minx) {minx = X1;}
		if (X2 < minx) {minx = X2;}
		if (Y1 < miny) {miny = Y1;}
		if (Y2 < miny) {miny = Y2;}

		/*找到三个点最大的X、Y坐标*/
		if (X1 > maxx) {maxx = X1;}
		if (X2 > maxx) {maxx = X2;}
		if (Y1 > maxy) {maxy = Y1;}
		if (Y2 > maxy) {maxy = Y2;}

		/*最小最大坐标之间的矩形为可能需要填充的区域*/
		/*遍历此区域中所有的点*/
		/*遍历X坐标*/
		for (i = minx; i <= maxx; i ++)
		{
			/*遍历Y坐标*/
			for (j = miny; j <= maxy; j ++)
			{
				/*调用OLED_pnpoly，判断指定点是否在指定三角形之中*/
				/*如果在，则画点，如果不在，则不做处理*/
				if (LCD_pnpoly(3, vx, vy, i, j)) {LCD_DrawPoint(i, j ,color);}
			}
		}
	}
}

/**
  * 函    数：LCD 画圆
  * 参    数：X 指定圆的圆心横坐标
  * 参    数：Y 指定圆的圆心纵坐标
  * 参    数：Radius 指定圆的半径，范围：0~255
  * 参    数：IsFilled 指定圆是否填充
  *           范围：LCD_UNFILLED		不填充
  *                 LCD_FILLED			填充
  * 返 回 值：无
  */
void LCD_DrawCircle(uint8_t X, uint8_t Y, uint8_t Radius, uint16_t color,uint8_t IsFilled)
{
	int16_t x, y, d, j;

	/*使用Bresenham算法画圆，可以避免耗时的浮点运算，效率更高*/
	/*参考文档：https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
	/*参考教程：https://www.bilibili.com/video/BV1VM4y1u7wJ*/

	d = 1 - Radius;
	x = 0;
	y = Radius;

	/*画每个八分之一圆弧的起始点*/
	LCD_DrawPoint(X + x, Y + y,color);
	LCD_DrawPoint(X - x, Y - y,color);
	LCD_DrawPoint(X + y, Y + x,color);
	LCD_DrawPoint(X - y, Y - x,color);

	if (IsFilled)		//指定圆填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在指定区域画点，填充部分圆*/
			LCD_DrawPoint(X, Y + j,color);
		}
	}

	while (x < y)		//遍历X轴的每个点
	{
		x ++;
		if (d < 0)		//下一个点在当前点东方
		{
			d += 2 * x + 1;
		}
		else			//下一个点在当前点东南方
		{
			y --;
			d += 2 * (x - y) + 1;
		}

		/*画每个八分之一圆弧的点*/
		LCD_DrawPoint(X + x, Y + y,color);
		LCD_DrawPoint(X + y, Y + x,color);
		LCD_DrawPoint(X - x, Y - y,color);
		LCD_DrawPoint(X - y, Y - x,color);
		LCD_DrawPoint(X + x, Y - y,color);
		LCD_DrawPoint(X + y, Y - x,color);
		LCD_DrawPoint(X - x, Y + y,color);
		LCD_DrawPoint(X - y, Y + x,color);

		if (IsFilled)	//指定圆填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点，填充部分圆*/
				LCD_DrawPoint(X + x, Y + j,color);
				LCD_DrawPoint(X - x, Y + j,color);
			}

			/*遍历两侧部分*/
			for (j = -x; j < x; j ++)
			{
				/*在指定区域画点，填充部分圆*/
				LCD_DrawPoint(X - y, Y + j,color);
				LCD_DrawPoint(X + y, Y + j,color);
			}
		}
	}
}

/**
  * 函    数：LCD 画椭圆
  * 参    数：X 指定椭圆的圆心横坐标
  * 参    数：Y 指定椭圆的圆心纵坐标
  * 参    数：A 指定椭圆的横向半轴长度，范围：0~255
  * 参    数：B 指定椭圆的纵向半轴长度，范围：0~255
  * 参    数：IsFilled 指定椭圆是否填充
  *           范围：LCD_UNFILLED		不填充
  *                 LCD_FILLED			填充
  * 返 回 值：无
  */
void LCD_DrawEllipse(uint8_t X, uint8_t Y, uint8_t A, uint8_t B, uint16_t color, uint8_t IsFilled)
{
	int16_t x, y, j;
	int16_t a = A, b = B;
	float d1, d2;

	/*使用Bresenham算法画椭圆，可以避免部分耗时的浮点运算，效率更高*/
	/*参考链接：https://blog.csdn.net/myf_666/article/details/128167392*/

	x = 0;
	y = b;
	d1 = b * b + a * a * (-b + 0.5);

	if (IsFilled)	//指定椭圆填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在指定区域画点，填充部分椭圆*/
			LCD_DrawPoint(X, Y + j,color);
			LCD_DrawPoint(X, Y + j,color);
		}
	}

	/*画椭圆弧的起始点*/
	LCD_DrawPoint(X + x, Y + y,color);
	LCD_DrawPoint(X - x, Y - y,color);
	LCD_DrawPoint(X - x, Y + y,color);
	LCD_DrawPoint(X + x, Y - y,color);

	/*画椭圆中间部分*/
	while (b * b * (x + 1) < a * a * (y - 0.5))
	{
		if (d1 <= 0)		//下一个点在当前点东方
		{
			d1 += b * b * (2 * x + 3);
		}
		else				//下一个点在当前点东南方
		{
			d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2);
			y --;
		}
		x ++;

		if (IsFilled)	//指定椭圆填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点，填充部分椭圆*/
				LCD_DrawPoint(X + x, Y + j,color);
				LCD_DrawPoint(X - x, Y + j,color);
			}
		}

		/*画椭圆中间部分圆弧*/
		LCD_DrawPoint(X + x, Y + y,color);
		LCD_DrawPoint(X - x, Y - y,color);
		LCD_DrawPoint(X - x, Y + y,color);
		LCD_DrawPoint(X + x, Y - y,color);
	}

	/*画椭圆两侧部分*/
	d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;

	while (y > 0)
	{
		if (d2 <= 0)		//下一个点在当前点东方
		{
			d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
			x ++;

		}
		else				//下一个点在当前点东南方
		{
			d2 += a * a * (-2 * y + 3);
		}
		y --;

		if (IsFilled)	//指定椭圆填充
		{
			/*遍历两侧部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点，填充部分椭圆*/
				LCD_DrawPoint(X + x, Y + j,color);
				LCD_DrawPoint(X - x, Y + j,color);
			}
		}

		/*画椭圆两侧部分圆弧*/
		LCD_DrawPoint(X + x, Y + y,color);
		LCD_DrawPoint(X - x, Y - y,color);
		LCD_DrawPoint(X - x, Y + y,color);
		LCD_DrawPoint(X + x, Y - y,color);
	}
}

/**
  * 函    数：判断指定点是否在指定角度内部
  * 参    数：X Y 指定点的坐标
  * 参    数：StartAngle EndAngle 起始角度和终止角度，范围：-180~180
  *           水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
  * 返 回 值：指定点是否在指定角度内部，1：在内部，0：不在内部
  */
uint8_t LCD_IsInAngle(int16_t X, int16_t Y, int16_t StartAngle, int16_t EndAngle)
{
	int16_t PointAngle;
	PointAngle = atan2(Y, X) / 3.14 * 180;	//计算指定点的弧度，并转换为角度表示
	if (StartAngle < EndAngle)	//起始角度小于终止角度的情况
	{
		/*如果指定角度在起始终止角度之间，则判定指定点在指定角度*/
		if (PointAngle >= StartAngle && PointAngle <= EndAngle)
		{
			return 1;
		}
	}
	else			//起始角度大于于终止角度的情况
	{
		/*如果指定角度大于起始角度或者小于终止角度，则判定指定点在指定角度*/
		if (PointAngle >= StartAngle || PointAngle <= EndAngle)
		{
			return 1;
		}
	}
	return 0;		//不满足以上条件，则判断判定指定点不在指定角度
}

/**
  * 函    数：LCD 画圆弧
  * 参    数：X 指定圆弧的圆心横坐标
  * 参    数：Y 指定圆弧的圆心纵坐标
  * 参    数：Radius 指定圆弧的半径，范围：0~255
  * 参    数：StartAngle 指定圆弧的起始角度，范围：-180~180
  *           水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
  * 参    数：EndAngle 指定圆弧的终止角度，范围：-180~180
  *           水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
  * 参    数：IsFilled 指定圆弧是否填充，填充后为扇形
  *           范围：LCD_UNFILLED		不填充
  *                 LCD_FILLED			填充
  * 返 回 值：无
  */
void LCD_DrawArc(uint8_t X, uint8_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint16_t color, uint8_t IsFilled)
{
	int16_t x, y, d, j;

	/*此函数借用Bresenham算法画圆的方法*/

	d = 1 - Radius;
	x = 0;
	y = Radius;

	/*在画圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理*/
	if (LCD_IsInAngle(x, y, StartAngle, EndAngle))	{LCD_DrawPoint(X + x, Y + y ,color);}
	if (LCD_IsInAngle(-x, -y, StartAngle, EndAngle)) {LCD_DrawPoint(X - x, Y - y ,color);}
	if (LCD_IsInAngle(y, x, StartAngle, EndAngle)) {LCD_DrawPoint(X + y, Y + x ,color);}
	if (LCD_IsInAngle(-y, -x, StartAngle, EndAngle)) {LCD_DrawPoint(X - y, Y - x ,color);}

	if (IsFilled)	//指定圆弧填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在填充圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理*/
			if (LCD_IsInAngle(0, j, StartAngle, EndAngle)) {LCD_DrawPoint(X, Y + j ,color);}
		}
	}

	while (x < y)		//遍历X轴的每个点
	{
		x ++;
		if (d < 0)		//下一个点在当前点东方
		{
			d += 2 * x + 1;
		}
		else			//下一个点在当前点东南方
		{
			y --;
			d += 2 * (x - y) + 1;
		}

		/*在画圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理*/
		if (LCD_IsInAngle(x, y, StartAngle, EndAngle)) {LCD_DrawPoint(X + x, Y + y ,color);}
		if (LCD_IsInAngle(y, x, StartAngle, EndAngle)) {LCD_DrawPoint(X + y, Y + x ,color);}
		if (LCD_IsInAngle(-x, -y, StartAngle, EndAngle)) {LCD_DrawPoint(X - x, Y - y ,color);}
		if (LCD_IsInAngle(-y, -x, StartAngle, EndAngle)) {LCD_DrawPoint(X - y, Y - x ,color);}
		if (LCD_IsInAngle(x, -y, StartAngle, EndAngle)) {LCD_DrawPoint(X + x, Y - y ,color);}
		if (LCD_IsInAngle(y, -x, StartAngle, EndAngle)) {LCD_DrawPoint(X + y, Y - x ,color);}
		if (LCD_IsInAngle(-x, y, StartAngle, EndAngle)) {LCD_DrawPoint(X - x, Y + y ,color);}
		if (LCD_IsInAngle(-y, x, StartAngle, EndAngle)) {LCD_DrawPoint(X - y, Y + x ,color);}

		if (IsFilled)	//指定圆弧填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在填充圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理*/
				if (LCD_IsInAngle(x, j, StartAngle, EndAngle)) {LCD_DrawPoint(X + x, Y + j ,color);}
				if (LCD_IsInAngle(-x, j, StartAngle, EndAngle)) {LCD_DrawPoint(X - x, Y + j ,color);}
			}

			/*遍历两侧部分*/
			for (j = -x; j < x; j ++)
			{
				/*在填充圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理*/
				if (LCD_IsInAngle(-y, j, StartAngle, EndAngle)) {LCD_DrawPoint(X - y, Y + j ,color);}
				if (LCD_IsInAngle(y, j, StartAngle, EndAngle)) {LCD_DrawPoint(X + y, Y + j ,color);}
			}
		}
	}
}

/******************************************************************************
      函数说明：显示汉字串
      入口数据：x,y显示坐标
                *s 要显示的汉字串
                fc 字的颜色
                bc 字的背景色
                sizey 字号 可选 16 24 32
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChinese(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	while(*s!=0)
	{
		if(sizey==12)       LCD_ShowChinese12x12(x,y,s,fc,bc,sizey,mode);
		else if(sizey==16)  LCD_ShowChinese16x16(x,y,s,fc,bc,sizey,mode);
		else if(sizey==24)  LCD_ShowChinese24x24(x,y,s,fc,bc,sizey,mode);
		else if(sizey==32)  LCD_ShowChinese32x32(x,y,s,fc,bc,sizey,mode);
		else return;
        s+=1;
        x+=sizey/3;
	}
}

/******************************************************************************
      函数说明：显示单个12x12汉字
      入口数据：x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChinese12x12(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;

	HZnum=sizeof(tfont12)/sizeof(typFNT_GB12);	//统计汉字数目
	for(k=0;k<HZnum;k++)
	{
		if((tfont12[k].Index[0]==*(s))&&(tfont12[k].Index[1]==*(s+1)))
		{
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{
					if(!mode)//非叠加方式
					{
						if(tfont12[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont12[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}
		continue;  //查找到对应点阵字库立即退出，防止多个汉字重复取模带来影响
	}
}

/******************************************************************************
      函数说明：显示单个16x16汉字
      入口数据：x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChinese16x16(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
  TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont16)/sizeof(typFNT_GB16);	//统计汉字数目
	for(k=0;k<HZnum;k++)
	{
		if ((tfont16[k].Index[0]==*(s))&&(tfont16[k].Index[1]==*(s+1)))
		{
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{
					if(!mode)//非叠加方式
					{
						if(tfont16[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont16[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}
		continue;  //查找到对应点阵字库立即退出，防止多个汉字重复取模带来影响
	}
}


/******************************************************************************
      函数说明：显示单个24x24汉字
      入口数据：x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChinese24x24(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont24)/sizeof(typFNT_GB24);	//统计汉字数目
	for(k=0;k<HZnum;k++)
	{
		if ((tfont24[k].Index[0]==*(s))&&(tfont24[k].Index[1]==*(s+1)))
		{
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{
					if(!mode)//非叠加方式
					{
						if(tfont24[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont24[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}
		continue;  //查找到对应点阵字库立即退出，防止多个汉字重复取模带来影响
	}
}

/******************************************************************************
      函数说明：显示单个32x32汉字
      入口数据：x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChinese32x32(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont32)/sizeof(typFNT_GB32);	//统计汉字数目
	for(k=0;k<HZnum;k++)
	{
		if ((tfont32[k].Index[0]==*(s))&&(tfont32[k].Index[1]==*(s+1)))
		{
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{
					if(!mode)//非叠加方式
					{
						if(tfont32[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont32[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}
		continue;  //查找到对应点阵字库立即退出，防止多个汉字重复取模带来影响
	}
}


/******************************************************************************
      函数说明：显示单个字符
      入口数据：x,y显示坐标
                num 要显示的字符
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowChar(u16 x,u16 y,u8 num,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 temp,sizex,t,m=0;
	u16 i,TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	sizex=sizey/2;
	TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey;
	num=num-' ';    //得到偏移后的值
	LCD_Address_Set(x,y,x+sizex-1,y+sizey-1);  //设置光标位置
	for(i=0;i<TypefaceNum;i++)
	{
		if(sizey==12)temp=ascii_1206[num][i];		       //调用6x12字体
		else if(sizey==16)temp=ascii_1608[num][i];		 //调用8x16字体
		else if(sizey==24)temp=ascii_2412[num][i];		 //调用12x24字体
		else if(sizey==32)temp=ascii_3216[num][i];		 //调用16x32字体
		else return;
		for(t=0;t<8;t++)
		{
			if(!mode)//非叠加模式
			{
				if(temp&(0x01<<t))LCD_WR_DATA(fc);
				else LCD_WR_DATA(bc);
				m++;
				if(m%sizex==0)
				{
					m=0;
					break;
				}
			}
			else//叠加模式
			{
				if(temp&(0x01<<t))LCD_DrawPoint(x,y,fc);//画一个点
				x++;
				if((x-x0)==sizex)
				{
					x=x0;
					y++;
					break;
				}
			}
		}
	}
}

/******************************************************************************
      函数说明：显示字符串
      入口数据：x,y显示坐标
                *p 要显示的字符串
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值：  无
******************************************************************************/
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	while(*p!='\0')
	{
		LCD_ShowChar(x,y,*p,fc,bc,sizey,mode);
		x+=sizey/2;
		p++;
	}
}


/******************************************************************************
      函数说明：显示数字
      入口数据：m底数，n指数
      返回值：  无
******************************************************************************/
u32 mypow(u8 m,u8 n)
{
	u32 result=1;
	while(n--)result*=m;
	return result;
}


/******************************************************************************
      函数说明：显示整数变量
      入口数据：x,y显示坐标
                num 要显示整数变量
                len 要显示的位数
                fc 字的颜色
                bc 字的背景色
                sizey 字号
      返回值：  无
******************************************************************************/
void LCD_ShowIntNum(u16 x,u16 y,u16 num,u8 len,u16 fc,u16 bc,u8 sizey)
{
	u8 t,temp;
	u8 enshow=0;
	u8 sizex=sizey/2;
	for(t=0;t<len;t++)
	{
		temp=(num/mypow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				LCD_ShowChar(x+t*sizex,y,' ',fc,bc,sizey,0);
				continue;
			}else enshow=1;

		}
	 	LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
	}
}


/******************************************************************************
      函数说明：显示两位小数变量
      入口数据：x,y显示坐标
                num 要显示小数变量
                len 要显示的位数
                fc 字的颜色
                bc 字的背景色
                sizey 字号
      返回值：  无
******************************************************************************/
void LCD_ShowFloatNum(u16 x,u16 y,float num,u8 len,u16 fc,u16 bc,u8 sizey)
{
	u8 t,temp,sizex;
	u16 num1;
	sizex=sizey/2;
	num1=num*100;
	for(t=0;t<len;t++)
	{
		temp=(num1/mypow(10,len-t-1))%10;
		if(t==(len-2))
		{
			LCD_ShowChar(x+(len-2)*sizex,y,'.',fc,bc,sizey,0);
			t++;
			len+=1;
		}
	 	LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
	}
}


/******************************************************************************
      函数说明：显示图片
      入口数据：x,y起点坐标
                length 图片长度
                width  图片宽度
                pic[]  图片数组
      返回值：  无
******************************************************************************/
void LCD_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[])
{
	u16 i,j;
	u32 k=0;
	LCD_Address_Set(x,y,x+length-1,y+width-1);
	for(i=0;i<length;i++)
	{
		for(j=0;j<width;j++)
		{
			LCD_WR_DATA8(pic[k*2]);
			LCD_WR_DATA8(pic[k*2+1]);
			k++;
		}
	}
}


