K1Guitar/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c

2017 lines
62 KiB
C
Raw Normal View History

#include "includes.h"
#include "Drv_ILI9341_Lcd_EnglishFont.h"
#include "Drv_ILI9341_Lcd_ChineseFont.h"
#include "w25q128.h"
#define PIC_BATCH_SIZE 1024
static uint8_t LCD_CnFontMetrics(uint8_t sizey, uint8_t *sizex, uint16_t *typeface_num)
{
switch (sizey)
{
case 8: *sizex = 8; *typeface_num = 8; return 1;
case 11: *sizex = 11; *typeface_num = 22; return 1;
case 12: *sizex = 12; *typeface_num = 24; return 1;
case 13: *sizex = 13; *typeface_num = 26; return 1;
case 15: *sizex = 15; *typeface_num = 30; return 1;
case 16: *sizex = 16; *typeface_num = 32; return 1;
case 24: *sizex = 24; *typeface_num = 72; return 1;
case 28: *sizex = 28; *typeface_num = 112; return 1;
default: return 0;
}
}
static uint8_t LCD_GetCnFontByte(uint8_t sizey, uint8_t index, uint16_t i)
{
switch (sizey)
{
case 8: return Chinese_font_8[index][i];
case 11: return Chinese_font_11[index][i];
case 12: return Chinese_font_12[index][i];
case 13: return Chinese_font_13[index][i];
case 15: return Chinese_font_15[index][i];
case 16: return Chinese_font_16[index][i];
case 24: return Chinese_font_24[index][i];
case 28: return Chinese_font_28[index][i];
default: return 0;
}
}
static uint8_t LCD_GetAsciiFontByte(uint8_t sizey, uint8_t num, uint16_t i)
{
switch (sizey)
{
case 8: return ascii_84[num][i];
case 11: return ascii_115[num][i];
case 12: return ascii_126[num][i];
case 13: return ascii_1306[num][i];
case 15: return ascii_157[num][i];
case 16: return ascii_1608[num][i];
case 24: return ascii_2412[num][i];
case 28: return ascii_2814[num][i];
default: return 0;
}
}
static const char (*LCD_GetChsTable(uint8_t sizey))[2]
{
switch (sizey)
{
case 8: return CHS8Table;
case 11: return CHS11Table;
case 12: return CHS12Table;
case 13: return CHS13Table;
case 15: return CHS15Table;
case 16: return CHS16Table;
case 24: return CHS24Table;
case 28: return CHS24Table;
default: return NULL;
}
}
static uint16_t LCD_GetChsTableSize(uint8_t sizey)
{
switch (sizey)
{
case 8: return (uint16_t)(sizeof(CHS8Table) / 2);
case 11: return (uint16_t)(sizeof(CHS11Table) / 2);
case 12: return (uint16_t)(sizeof(CHS12Table) / 2);
case 13: return (uint16_t)(sizeof(CHS13Table) / 2);
case 15: return (uint16_t)(sizeof(CHS15Table) / 2);
case 16: return (uint16_t)(sizeof(CHS16Table) / 2);
case 24: return (uint16_t)(sizeof(CHS24Table) / 2);
case 28: return (uint16_t)(sizeof(CHS24Table) / 2);
default: return 0;
}
}
static uint8_t LCD_GetAsciiMetrics(uint8_t size, uint8_t *char_width, uint8_t *char_height)
{
*char_height = size;
switch (size)
{
case 8: *char_width = 4; return 1;
case 11: *char_width = 5; return 1;
case 12: *char_width = 6; return 1;
case 13: *char_width = 6; return 1;
case 15: *char_width = 7; return 1;
case 16: *char_width = 8; return 1;
case 24: *char_width = 12; return 1;
case 28: *char_width = 14; return 1;
default: return 0;
}
}
//r:0-31 g:0-63 b:0-31
uint16_t RGB(int r,int g,int b)
{
return r << 11 | g << 5 | b;
}
// SPI轮询批量发送无DMA开销适合10~100字节的小批量数据
void LCD_SPI_Write(const uint8_t *data, uint32_t len)
{
uint32_t tmp = len;
while (tmp--) {
while (!(SPI2->sts & SPI_I2S_TDBE_FLAG));
SPI2->dt = *(data++);
}
while (SPI2->sts & SPI_I2S_BF_FLAG);
}
static uint8_t pic_batch_buf[PIC_BATCH_SIZE];
// 在指定位置显示图片
void LCD_WR_PIC(uint16_t x, uint16_t y, uint16_t length, uint16_t width, const uint8_t data[])
{
// 1. 计算总像素数和总字节数每个像素2字节RGB565
uint32_t total_pixels = (uint32_t)length * width;
uint32_t total_bytes = total_pixels * 2;
if (total_bytes == 0) return;
// 2. 设置显示区域
LCD_SetWindow(x, y, x + length - 1, y + width - 1);
// 3. 手动设置DC为数据模式批量传输期间保持
LCD_DC_Set();
// 4. 批量DMA传输数据直接从源地址发送无需中间拷贝
// 注意data 指向 Flash 中的 const 数组STM32F1 DMA 可直接从 Flash 读取
uint32_t remaining_bytes = total_bytes;
uint32_t pic_offset = 0;
while (remaining_bytes > 0)
{
uint32_t transmit_bytes = (remaining_bytes >= PIC_BATCH_SIZE) ? PIC_BATCH_SIZE : remaining_bytes;
/* DMA 直接从源数据地址发送(无中间拷贝) */
LCD_SPI_DMA_Send(data + pic_offset, transmit_bytes);
pic_offset += transmit_bytes;
remaining_bytes -= transmit_bytes;
}
}
/* 跳过近黑色像素(图标导出底色常为 0x0000/0x0001/0x0022非纯 BLACK */
void LCD_WR_PIC_Trans(uint16_t x, uint16_t y, uint16_t length, uint16_t width,
const uint8_t data[], uint16_t key_color)
{
uint32_t total_pixels = (uint32_t)length * width;
uint32_t i;
(void)key_color;
if (total_pixels == 0)
return;
for (i = 0; i < total_pixels; i++)
{
uint8_t hi = data[i * 2];
uint8_t lo = data[i * 2 + 1];
uint16_t c = (uint16_t)((hi << 8) | lo);
uint8_t r5 = (uint8_t)((c >> 11) & 0x1F);
uint8_t g6 = (uint8_t)((c >> 5) & 0x3F);
uint8_t b5 = (uint8_t)(c & 0x1F);
/* 近黑视为透明,避免图标黑底方块 */
if (r5 <= 1 && g6 <= 2 && b5 <= 2)
continue;
LCD_DrawPoint((uint16_t)(x + (i % length)),
(uint16_t)(y + (i / length)),
c);
}
}
/******************************************************************************
W25Q128外部Flash读取图片数据并显示
x,y
length
width
flash_addr W25Q128中的起始地址
RGB5652
******************************************************************************/
void LCD_WR_PIC_FROM_FLASH(uint16_t x, uint16_t y, uint16_t length, uint16_t width, uint32_t flash_addr)
{
// 1. 计算总像素数和总字节数
uint32_t total_pixels = (uint32_t)length * width;
uint32_t total_bytes = total_pixels * 2;
if (total_bytes == 0) return;
// 2. 设置显示区域
LCD_SetWindow(x, y, x + length - 1, y + width - 1);
// 3. 手动设置DC为数据模式批量传输期间保持
LCD_DC_Set();
// 4. 分批从W25Q128读取并发送到LCD
uint32_t remaining_bytes = total_bytes;
uint32_t flash_offset = 0;
while (remaining_bytes > 0)
{
uint32_t batch_size = (remaining_bytes >= PIC_BATCH_SIZE) ? PIC_BATCH_SIZE : remaining_bytes;
// 从W25Q128读取一批数据到内存缓冲区SPI1阻塞读
W25Q128_Read(pic_batch_buf, flash_addr + flash_offset, (uint16_t)batch_size);
// 通过SPI2 DMA发送到LCD内部等待完成
LCD_SPI_DMA_Send(pic_batch_buf, batch_size);
flash_offset += batch_size;
remaining_bytes -= batch_size;
}
}
/* 只绘制图片左侧 clip_w 列(电池电量按百分比裁剪) */
void LCD_WR_PIC_ClipX(uint16_t x, uint16_t y, uint16_t full_w, uint16_t h,
uint16_t clip_w, const uint8_t data[])
{
uint16_t row;
if (clip_w == 0 || full_w == 0 || h == 0) return;
if (clip_w > full_w) clip_w = full_w;
LCD_SetWindow(x, y, x + clip_w - 1, y + h - 1);
LCD_DC_Set();
for (row = 0; row < h; row++)
{
LCD_SPI_Write(data + (uint32_t)row * full_w * 2, (uint32_t)clip_w * 2);
}
}
/* 从W25Q128读小图到RAM后透明显示跳过近黑像素仅用于 <=4KB 小图 */
#define PIC_TRANS_BUF_SIZE 4096
void LCD_WR_PIC_FROM_FLASH_Trans(uint16_t x, uint16_t y, uint16_t length, uint16_t width, uint32_t flash_addr)
{
static uint8_t s_trans_buf[PIC_TRANS_BUF_SIZE];
uint32_t total_bytes = (uint32_t)length * width * 2;
uint32_t i;
if (total_bytes == 0 || total_bytes > PIC_TRANS_BUF_SIZE) return;
W25Q128_Read(s_trans_buf, flash_addr, (uint16_t)total_bytes);
for (i = 0; i < total_bytes / 2; i++)
{
uint16_t c = (uint16_t)((s_trans_buf[i * 2] << 8) | s_trans_buf[i * 2 + 1]);
uint8_t r5 = (uint8_t)((c >> 11) & 0x1F);
uint8_t g6 = (uint8_t)((c >> 5) & 0x3F);
uint8_t b5 = (uint8_t)(c & 0x1F);
if (r5 <= 1 && g6 <= 2 && b5 <= 2)
continue;
LCD_DrawPoint((uint16_t)(x + (i % length)), (uint16_t)(y + (i / length)), c);
}
}
/******************************************************************************
xsta,ysta
xend,yend
color
******************************************************************************/
//void LCD_FillByColor(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color)
//{
// uint16_t i,j,fill;
// uint8_t u8ColorTmp[960];
//
// LCD_SetWindow(xsta,ysta,xend-1,yend-1);//设置显示范围
//
// for(i=ysta;i<yend;i++)
// {
// for(j=xsta;j<xend;j++)
// {
// LCD_WR_DATA16(color);
// }
// }
// return ;
//}
////// 把缓冲区改为全局/静态(避免栈溢出)
uint8_t lcd_fill_buf[2048]; // 1024字节 = 512个像素减少DMA批次
////
void LCD_FillByColor(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color)
{
if(xsta >= xend || ysta >= yend)
{
return;
}
uint32_t total_pixel;
int i;
// 颜色拆分16位RGB565拆分为高低8位
uint8_t ch = (color >> 8) & 0xFF;
uint8_t cl = color & 0xFF;
int buf_pixels = sizeof(lcd_fill_buf) / 2; // 1024个像素2048字节
// 预填充颜色缓冲区每个像素2字节高8位+低8位
for (i = 0; i < buf_pixels; i++) {
lcd_fill_buf[i * 2] = ch;
lcd_fill_buf[i * 2 + 1] = cl;
}
// 设置屏幕填充窗口(告诉屏幕要填充的区域)
LCD_SetWindow(xsta, ysta, xend - 1, yend - 1);
// 切换到数据模式DC=1屏幕接收像素数据
LCD_DC_Set();
// 计算总像素数
total_pixel = (uint32_t)(xend - xsta) * (yend - ysta);
// 批量DMA发送像素数据
while (total_pixel > 0) {
int send_pixels = (total_pixel > buf_pixels) ? buf_pixels : total_pixel;
int send_bytes = send_pixels * 2;
/* DMA 发送(内部等待完成) */
LCD_SPI_DMA_Send(lcd_fill_buf, send_bytes);
total_pixel -= send_pixels;
}
// 恢复DC为指令模式避免后续指令被当数据处理
LCD_DC_Clr();
}
/******************************************************************************
xsta,ysta
xend,yend
color_top RGB565
color_bottom RGB565
R/G/B lcd_fill_buf
(0x5AEB) (0x0000)
******************************************************************************/
void LCD_FillGradient(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend,
uint16_t color_top, uint16_t color_bottom)
{
if (xsta >= xend || ysta >= yend) return;
uint16_t height = yend - ysta;
uint16_t width = xend - xsta;
if (width == 0 || height == 0) return;
// 拆分解顶底颜色的 R/G/B 分量RGB565
uint16_t r_top = (color_top >> 11) & 0x1F;
uint16_t g_top = (color_top >> 5) & 0x3F;
uint16_t b_top = color_top & 0x1F;
uint16_t r_bot = (color_bottom >> 11) & 0x1F;
uint16_t g_bot = (color_bottom >> 5) & 0x3F;
uint16_t b_bot = color_bottom & 0x1F;
// 只用 SetWindow 设一次整个区域GRAM 地址自动递增
LCD_SetWindow(xsta, ysta, xend - 1, yend - 1);
LCD_DC_Set();
// 用 lcd_fill_buf 逐行发送,一行 240 像素 = 480 字节 < 700不会溢出
int row_bytes = width * 2;
// 高度为1时直接填顶部颜色防止除零
if (height <= 1)
{
uint8_t ch = (color_top >> 8) & 0xFF;
uint8_t cl = color_top & 0xFF;
for (int i = 0; i < width; i++) {
lcd_fill_buf[i * 2] = ch;
lcd_fill_buf[i * 2 + 1] = cl;
}
LCD_SPI_Write(lcd_fill_buf, row_bytes);
LCD_DC_Clr();
return;
}
// 逐行填充:纯整数运算 val = top + (bot - top) * row / (height - 1)
for (uint16_t row = 0; row < height; row++)
{
uint16_t r = r_top + (r_bot - r_top) * row / (height - 1);
uint16_t g = g_top + (g_bot - g_top) * row / (height - 1);
uint16_t b = b_top + (b_bot - b_top) * row / (height - 1);
uint16_t color = (r << 11) | (g << 5) | b;
uint8_t ch = (color >> 8) & 0xFF;
uint8_t cl = color & 0xFF;
// 填充行缓冲
for (int i = 0; i < width; i++) {
lcd_fill_buf[i * 2] = ch;
lcd_fill_buf[i * 2 + 1] = cl;
}
// 发送该行SPI 轮询)
LCD_SPI_Write(lcd_fill_buf, row_bytes);
}
LCD_DC_Clr();
}
/******************************************************************************
x,y
color
******************************************************************************/
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color)
{
LCD_SetWindow(x, y, x, y);//设置光标位置
LCD_WR_DATA16(color);
}
/******************************************************************************
线
x1,y1
x2,y2
color 线
******************************************************************************/
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
// ===== 水平/垂直线快速通道(用批量填充代替逐点画) =====
if (y1 == y2)
{
if (x1 > x2) { uint16_t tmp = x1; x1 = x2; x2 = tmp; }
LCD_FillByColor(x1, y1, x2 + 1, y1 + 1, color);
return;
}
if (x1 == x2)
{
if (y1 > y2) { uint16_t tmp = y1; y1 = y2; y2 = tmp; }
LCD_FillByColor(x1, y1, x1 + 1, y2 + 1, color);
return;
}
uint16_t t;
int xerr = 0, yerr = 0, delta_x, delta_y, distance;
int incx, incy, uRow, uCol;
delta_x = x2 - x1; //计算坐标增量
delta_y = y2 - y1;
uRow = x1;//画线起点坐标
uCol = y1;
if(delta_x > 0)
{
incx = 1; //设置单步方向
}
else if (delta_x == 0)
{
incx = 0;//垂直线
}
else
{
incx=-1;
delta_x = -delta_x;
}
if(delta_y > 0)
{
incy = 1;
}
else if (delta_y == 0)
{
incy = 0;//水平线
}
else
{
incy = -1;
delta_y = -delta_y;
}
if(delta_x > delta_y)
{
distance = delta_x; //选取基本增量坐标轴
}
else
{
distance = delta_y;
}
for(t = 0; t < distance + 1; t++)
{
LCD_DrawPoint(uRow, uCol, color);//画点
xerr += delta_x;
yerr += delta_y;
if(xerr > distance)
{
xerr -= distance;
uRow += incx;
}
if(yerr > distance)
{
yerr -= distance;
uCol += incy;
}
}
}
//void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
//{
// for(uint16_t x = x1;x <= x2;x++)
// {
// LCD_DrawPoint(x,y1,color);
// }
//}
/******************************************************************************
x1,y1
x2,y2
color
******************************************************************************/
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t color)
{
LCD_DrawLine(x1, y1, x2, y1, color);
LCD_DrawLine(x1, y1, x1, y2, color);
LCD_DrawLine(x1, y2, x2, y2, color);
LCD_DrawLine(x2, y1, x2, y2, color);
}
/******************************************************************************
x0,y0
r
color
******************************************************************************/
void LCD_DrawCircle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color)
{
int a = 0, b = r;
while (a <= b)
{
// 画 8 个对称点(逐点绘制,保证左右两侧正确显示)
LCD_DrawPoint(x0 + b, y0 - a, color);
LCD_DrawPoint(x0 + b, y0 + a, color);
LCD_DrawPoint(x0 - b, y0 - a, color);
LCD_DrawPoint(x0 - b, y0 + a, color);
LCD_DrawPoint(x0 + a, y0 - b, color);
LCD_DrawPoint(x0 + a, y0 + b, color);
LCD_DrawPoint(x0 - a, y0 - b, color);
LCD_DrawPoint(x0 - a, y0 + b, color);
a++;
if ((a * a + b * b) > (r * r))
b--;
}
}
/******************************************************************************
x,y显示坐标
num
fc
bc
sizey
mode: 0 1
******************************************************************************/
void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t num, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
uint8_t sizex;
uint16_t i, TypefaceNum;
uint16_t x0 = x;
uint8_t char_h;
num = num - ' ';
if (!LCD_GetAsciiMetrics(sizey, &sizex, &char_h))
{
return;
}
TypefaceNum = (sizex / 8 + ((sizex % 8) ? 1 : 0)) * sizey;
// 一次性设置窗口LCD地址自动递增无需每像素都设
LCD_SetWindow(x, y, x + sizex - 1, y + sizey - 1);
LCD_DC_Set(); // 保持数据模式
if (!mode) // 非叠加模式(全字批量优化路径)
{
uint8_t fc_h = (fc >> 8) & 0xFF, fc_l = fc & 0xFF;
uint8_t bc_h = (bc >> 8) & 0xFF, bc_l = bc & 0xFF;
uint8_t temp;
uint8_t m = 0;
uint16_t buf_idx = 0;
for (i = 0; i < TypefaceNum; i++)
{
temp = LCD_GetAsciiFontByte(sizey, num, i);
for (uint8_t t = 0; t < 8; t++)
{
if (temp & (0x01 << t))
{
pic_batch_buf[buf_idx++] = fc_h;
pic_batch_buf[buf_idx++] = fc_l;
}
else
{
pic_batch_buf[buf_idx++] = bc_h;
pic_batch_buf[buf_idx++] = bc_l;
}
m++;
if (m == sizex) { m = 0; break; }
}
if (buf_idx + 16 > PIC_BATCH_SIZE) {
LCD_SPI_Write(pic_batch_buf, buf_idx);
buf_idx = 0;
}
}
if (buf_idx > 0)
LCD_SPI_Write(pic_batch_buf, buf_idx);
}
else // 叠加模式(保持逐点画,不改动)
{
uint8_t temp;
for (i = 0; i < TypefaceNum; i++)
{
temp = LCD_GetAsciiFontByte(sizey, num, i);
for (uint8_t t = 0; t < 8; t++)
{
if (temp & (0x01 << t))
LCD_DrawPoint(x, y, fc);
x++;
if ((x - x0) == sizex)
{
x = x0;
y++;
break;
}
}
}
}
LCD_DC_Clr();
}
void LCD_ShowChinese(uint16_t x, uint16_t y, uint8_t index, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
uint8_t sizex;
uint16_t i, TypefaceNum = 0;
uint16_t x0 = x;
if (!LCD_CnFontMetrics(sizey, &sizex, &TypefaceNum))
return;
// 一次性设置窗口
LCD_SetWindow(x, y, x + sizex - 1, y + sizey - 1);
LCD_DC_Set();
if (!mode) // 非叠加模式(全字批量优化路径)
{
uint8_t fc_h = (fc >> 8) & 0xFF, fc_l = fc & 0xFF;
uint8_t bc_h = (bc >> 8) & 0xFF, bc_l = bc & 0xFF;
uint8_t temp;
uint8_t m = 0;
uint16_t buf_idx = 0;
for (i = 0; i < TypefaceNum; i++)
{
temp = LCD_GetCnFontByte(sizey, index, i);
for (uint8_t t = 0; t < 8; t++)
{
if (temp & (0x80 >> t))
{
pic_batch_buf[buf_idx++] = fc_h;
pic_batch_buf[buf_idx++] = fc_l;
}
else
{
pic_batch_buf[buf_idx++] = bc_h;
pic_batch_buf[buf_idx++] = bc_l;
}
m++;
if (m == sizex) { m = 0; break; }
}
if (buf_idx + 16 > PIC_BATCH_SIZE) {
LCD_SPI_Write(pic_batch_buf, buf_idx);
buf_idx = 0;
}
}
if (buf_idx > 0)
LCD_SPI_Write(pic_batch_buf, buf_idx);
}
else // 叠加模式(保持逐点画)
{
uint8_t temp;
for (i = 0; i < TypefaceNum; i++)
{
temp = LCD_GetCnFontByte(sizey, index, i);
for (uint8_t t = 0; t < 8; t++)
{
if (temp & (0x80 >> t))
LCD_DrawPoint(x, y, fc);
x++;
if ((x - x0) == sizex)
{
x = x0;
y++;
break;
}
}
}
}
LCD_DC_Clr();
}
/******************************************************************************
x,y
w,h
r 5~12
color
******************************************************************************/
// 鍦嗚鐭╁舰鍗曡壊濉厖锛堜繚鐣欏師鏈夎皟鐢ㄨ<E990A2>呬笉鍙楀奖鍝嶏級
// 鏃х増瀹炵幇鍦ㄤ笅鏂规敞閲婂尯鍐呬繚鐣欙紝渚垮洖婊?
void LCD_FillRoundRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, uint16_t color)
{
int16_t x1 = x, y1 = y;
int16_t x2 = x + w - 1, y2 = y + h - 1;
if (r > (w / 2)) r = w / 2;
if (r > (h / 2)) r = h / 2;
LCD_FillByColor(x1 + r, y1, x2 - r + 1, y2 + 1, color);
LCD_FillByColor(x1, y1 + r, x1 + r, y2 - r + 1, color);
LCD_FillByColor(x2 - r + 1, y1 + r, x2 + 1, y2 - r + 1, color);
int16_t cx1 = x1 + r;
int16_t cy1 = y1 + r;
int16_t cx2 = x2 - r;
int16_t cy2 = y2 - r;
int16_t tx, ty, d;
tx = 0;
ty = r;
d = 3 - 2 * r;
uint8_t ch = (color >> 8) & 0xFF;
uint8_t cl = color & 0xFF;
while (tx <= ty)
{
LCD_DrawPoint(cx1 - ty, cy1 - tx, color);
LCD_DrawPoint(cx2 + ty, cy1 - tx, color);
LCD_DrawPoint(cx1 - tx, cy1 - ty, color);
LCD_DrawPoint(cx2 + tx, cy1 - ty, color);
LCD_DrawPoint(cx1 - ty, cy2 + tx, color);
LCD_DrawPoint(cx2 + ty, cy2 + tx, color);
LCD_DrawPoint(cx1 - tx, cy2 + ty, color);
LCD_DrawPoint(cx2 + tx, cy2 + ty, color);
if (d < 0) d += 4 * tx + 6;
else { d += 4 * (tx - ty) + 10; ty--; }
tx++;
}
for (ty = 0; ty < r; ty++)
{
int max_tx = -1;
for (tx = 0; tx < r; tx++) { if (tx * tx + ty * ty <= r * r) max_tx = tx; }
if (max_tx < 0) continue;
int row_pixels = max_tx + 1;
uint8_t row_buf[48];
for (tx = 0; tx < row_pixels; tx++) { row_buf[tx * 2] = ch; row_buf[tx * 2 + 1] = cl; }
int row_bytes = row_pixels * 2;
LCD_SetWindow(cx1 - max_tx, cy1 - ty, cx1, cy1 - ty); LCD_DC_Set(); LCD_SPI_Write(row_buf, row_bytes);
LCD_SetWindow(cx2, cy1 - ty, cx2 + max_tx, cy1 - ty); LCD_DC_Set(); LCD_SPI_Write(row_buf, row_bytes);
LCD_SetWindow(cx1 - max_tx, cy2 + ty, cx1, cy2 + ty); LCD_DC_Set(); LCD_SPI_Write(row_buf, row_bytes);
LCD_SetWindow(cx2, cy2 + ty, cx2 + max_tx, cy2 + ty); LCD_DC_Set(); LCD_SPI_Write(row_buf, row_bytes);
}
}
// =====================================================================
// 函数名LCD_FillRoundRectGradient
// 功能:绘制渐变圆角矩形,从上到下垂直渐变填充
//
// 入参说明:
// x, y 矩形左上角坐标
// w, h 矩形宽度、高度
// r 圆角半径
// color_top 顶部起始颜色RGB565格式
// color_bottom底部结束颜色RGB565格式
//
// 实现逻辑:
// 1. 逐行计算每行对应的渐变中间色值;
// 2. 计算圆角区域有效显示范围,仅渲染可见像素;
// 3. 调用单行填充接口 LCD_FillByColor 逐行绘制;
// 4. 逐行渲染保证垂直渐变过渡平滑无断层。
// =====================================================================
void LCD_FillRoundRectGradient(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
uint16_t r, uint16_t color_top, uint16_t color_bottom)
{
int16_t x1 = x, y1 = y;
int16_t x2 = x + w - 1, y2 = y + h - 1;
// 闄愬埗鍦嗚鏈<EE9D97>ぇ涓嶈秴杩囧崐瀹<E5B490>崐楂?
if (r > (w / 2)) r = w / 2;
if (r > (h / 2)) r = h / 2;
if (h <= 1) { LCD_FillByColor(x, y, x2 + 1, y2 + 1, color_top); return; }
// 鎷嗚В椤跺簳棰滆壊R/G/B鍒嗛噺
uint16_t r_top = (color_top >> 11) & 0x1F;
uint16_t g_top = (color_top >> 5) & 0x3F;
uint16_t b_top = color_top & 0x1F;
uint16_t r_bot = (color_bottom >> 11) & 0x1F;
uint16_t g_bot = (color_bottom >> 5) & 0x3F;
uint16_t b_bot = color_bottom & 0x1F;
// int16_t cx1 = x1 + r;
int16_t cy1 = y1 + r;
// int16_t cx2 = x2 - r;
int16_t cy2 = y2 - r;
// 娓愬彉琛屽鍏咃細閫犺璁畻璇ュ瞾棰滆壊鍜屽彲瑙<E5BDB2>寮韫堬紙鑰冭檻鍦嗚锛?
for (int16_t row_y = y1; row_y <= y2; row_y++)
{
// 璁$畻璇ヨ娓愬彉棰滆壊
uint16_t offset = row_y - y1;
uint16_t r_comp = r_top + (r_bot - r_top) * offset / (h - 1);
uint16_t g_comp = g_top + (g_bot - g_top) * offset / (h - 1);
uint16_t b_comp = b_top + (b_bot - b_top) * offset / (h - 1);
uint16_t color = (r_comp << 11) | (g_comp << 5) | b_comp;
// 璁$畻璇ヨ宸﹀彸搴旇烦杩囩殑鍍忕礌鏁帮紙鍦嗚閮ㄥ垎锛?
int16_t left_skip = 0;
int16_t right_skip = 0;
if (r > 0 && row_y < cy1)
{
// 椤堕儴鍦嗚
int16_t ty = cy1 - row_y - 1; // 0 ~ r-1
if (ty >= 0 && ty < r)
{
int max_tx = 0;
for (int16_t tx = 0; tx < r; tx++)
{
if (tx * tx + ty * ty <= r * r) max_tx = tx;
}
left_skip = r - max_tx - 1;
right_skip = left_skip;
}
}
else if (r > 0 && row_y > cy2)
{
// 搴曢儴鍦嗚
int16_t ty = row_y - cy2 - 1; // 0 ~ r-1
if (ty >= 0 && ty < r)
{
int max_tx = 0;
for (int16_t tx = 0; tx < r; tx++)
{
if (tx * tx + ty * ty <= r * r) max_tx = tx;
}
left_skip = r - max_tx - 1;
right_skip = left_skip;
}
}
// 濉厖璇ヨ鍙閮ㄥ垎
LCD_FillByColor(x1 + left_skip, row_y, x2 - right_skip + 1, row_y + 1, color);
}
}
//void LCD_FillRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t r, uint16_t color)
//{
// uint16_t x2 = x0 + w - 1;
// uint16_t y2 = y0 + h - 1;
// if(w == 0 || h == 0) return;
// if(r > w/2) r = w/2;
// if(r > h/2) r = w/2;
//
// // 主体填充
// LCD_FillByColor(x0, y0 + r, x2+1, y2 - r + 1, color);
// LCD_FillByColor(x0 + r, y0, x2 - r + 1, y0 + r + 1, color);
// LCD_FillByColor(x0 + r, y2 - r + 1, x2 - r + 1, y2 + 1, color);
//
// // 简易圆角(逐行填充,无复杂算法)
// for(uint16_t y = 0; y < r; y++)
// {
// for(uint16_t x = 0; x < r; x++)
// {
// if(x*x + y*y <= r*r)
// {
// LCD_DrawPoint(x0 + r - x, y0 + r - y, color);
// LCD_DrawPoint(x2 - r + x, y0 + r - y, color);
// LCD_DrawPoint(x0 + r - x, y2 - r + y, color);
// LCD_DrawPoint(x2 - r + x, y2 - r + y, color);
// }
// }
// }
//}
/**
* @brief /
* @param x1/y1: , x2/y2: , color: , fill: 1=/0=
*/
void LCD_DrawRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color, uint8_t fill)
{
uint16_t x, y;
if(fill)
{
for(y=y1; y<=y2; y++)
{
for(x=x1; x<=x2; x++)
{
LCD_DrawPoint(x, y, color);
}
}
}
else
{
// 画边框
for(x=x1; x<=x2; x++) {LCD_DrawPoint(x, y1, color); LCD_DrawPoint(x, y2, color);}
for(y=y1; y<=y2; y++) {LCD_DrawPoint(x1, y, color); LCD_DrawPoint(x2, y, color);}
}
}
/******************************************************************************
x,y显示坐标
*p
fc
bc
sizey
mode: 0 1
******************************************************************************/
void LCD_ShowString(uint16_t x, uint16_t y, const uint8_t *p, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
while(*p != '\0')
{
LCD_ShowChar(x, y, *p, fc, bc, sizey, mode);
x += sizey / 2;
p++;
}
}
uint16_t GetLength(const char* str) {
int i = 0;
while (*str++) {
i++;
}
return i;
}
/******************************************************************************
x,y
str
fc
bc
sizey
mode 0= 1=
******************************************************************************/
void LCD_ShowChineseString(uint16_t x, uint16_t y, uint8_t *str, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
uint16_t i;
int slen = GetLength((const char*)str);
char TmpS[2];
const char (*codeTab)[2] = NULL;
uint8_t xStep = 0;
uint16_t tabSize;
codeTab = LCD_GetChsTable(sizey);
if (codeTab == NULL)
{
return;
}
tabSize = LCD_GetChsTableSize(sizey);
if (!LCD_CnFontMetrics(sizey, &xStep, &i))
{
return;
}
/* xStep is character cell width (= sizey for square CN glyphs). */
xStep = sizey;
for (i = 0; i < (uint16_t)slen; )
{
if ((uint8_t)str[0] > 0x80)
{
uint16_t j;
uint8_t found = 0;
if (i + 1 >= (uint16_t)slen)
{
break;
}
TmpS[0] = (char)str[0];
TmpS[1] = (char)str[1];
for (j = 0; j < tabSize; j++)
{
if (TmpS[0] == codeTab[j][0] && TmpS[1] == codeTab[j][1])
{
LCD_ShowChinese(x, y, j, fc, bc, sizey, mode);
x += xStep;
found = 1;
break;
}
}
/* Always advance past this GBK character, even if glyph is missing. */
str += 2;
i += 2;
(void)found;
}
else
{
str += 1;
i += 1;
}
}
}
/******************************************************************************
******************************************************************************/
static uint16_t LCD_GetMixedStringWidth(uint8_t *str, uint8_t sizey)
{
uint16_t width = 0;
uint8_t xStepCn = sizey;
uint8_t xStepAscii = sizey / 2;
while(*str != '\0')
{
if(*str > 0x80)
{
width += xStepCn;
str += 2;
}
else
{
width += xStepAscii;
str += 1;
}
}
return width;
}
/******************************************************************************
ASCII和中文字符
//
x1,x2
y Y坐标
*str ASCII+GB2312编码
align LCD_ALIGN_LEFT / LCD_ALIGN_CENTER / LCD_ALIGN_RIGHT
fc
bc
sizey 12/16/24
mode 0 1
X坐标
******************************************************************************/
uint16_t LCD_ShowMixedString(uint16_t x1, uint16_t x2, uint16_t y, uint8_t *str, uint8_t align, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
const char (*codeTab)[2] = NULL;
uint8_t xStepCn = sizey;
uint8_t xStepAscii = sizey / 2;
uint16_t draw_x;
codeTab = LCD_GetChsTable(sizey);
if (codeTab == NULL)
return x1;
// 先计算字符串总像素宽度再根据对齐方式确定起始X坐标
uint16_t total_width = LCD_GetMixedStringWidth(str, sizey);
uint16_t area_width = (x2 > x1) ? (x2 - x1) : 0;
switch(align)
{
case LCD_ALIGN_LEFT:
default:
draw_x = x1;
break;
case LCD_ALIGN_CENTER:
draw_x = (total_width >= area_width) ? x1 : x1 + (area_width - total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
draw_x = (total_width >= area_width) ? x1 : x2 - total_width;
break;
}
// 记住起始X坐标对齐后的位置供返回
uint16_t start_x = draw_x;
// 渲染字符串
while(*str != '\0')
{
if(*str > 0x80)
{
uint8_t ch_high = str[0];
uint8_t ch_low = str[1];
if(ch_low == 0) break;
uint16_t maxIndex = LCD_GetChsTableSize(sizey);
for(uint16_t j = 0; j < maxIndex; j++)
{
if(ch_high == codeTab[j][0] && ch_low == codeTab[j][1])
{
LCD_ShowChinese(draw_x, y, j, fc, bc, sizey, mode);
break;
}
}
draw_x += xStepCn;
str += 2;
}
else
{
LCD_ShowChar(draw_x, y, *str, fc, bc, sizey, mode);
draw_x += xStepAscii;
str += 1;
}
}
return start_x;
}
/******************************************************************************
x,y显示坐标
*p
fc
bc
sizey
mode: 0 1
******************************************************************************/
void LCD_ClearString(uint16_t x, uint16_t y, const uint8_t *p, uint16_t bc, uint8_t sizey, uint8_t mode)
{
while (*p != '\0')
{
// 用背景色bc作为“前景色”显示空格字符覆盖原字符
LCD_ShowChar(x, y, ' ', bc, bc, sizey, mode); // 前景色=背景色=bc相当于清除
x += sizey / 2; // 与原显示函数x步长一致
p++;
}
}
///******************************************************************************
// 函数说明:自动对齐显示汉字字符串
// 入口数据:
// x 显示x轴起始坐标水平位置
// y 显示Y轴起始坐标垂直位置
// *p 要显示的字符串(需以'\0'结尾)
// align 对齐模式LCD_ALIGN_LEFT/LCD_ALIGN_CENTER/LCD_ALIGN_RIGHT
// fc 字的颜色(前景色)
// bc 字的背景色
// mode 显示模式0=非叠加1=叠加)
// 返回值: 无
// 核心参数固定:
// - 字体大小8*16字符宽度=8高度=16
// - 屏幕X轴范围0~240靠右对齐时贴X=240
// 注意: 1. 字符串必须以'\0'结尾,否则会读取垃圾数据导致显示异常
// 2. 若字符串总宽度超过240会自动向左溢出符合常规显示逻辑
// 3. 完全兼容原LCD_ShowChar函数显示效果与原逻辑一致
//******************************************************************************/
//void LCD_ShowChinese_AutoAlign(uint8_t x, uint16_t y, const uint8_t *p, uint8_t align,
// uint16_t fc, uint16_t bc, uint8_t mode, uint8_t size)
//{
// uint8_t CHAR_WIDTH = size;
// uint16_t str_total_width = 0;
// uint16_t curr_x = 0;
// const uint8_t *p_tmp = p;
//
// // ==========================
// // 汉字个数 × 宽度
// // ==========================
// while (*p_tmp != '\0')
// {
// if ((uint8_t)*p_tmp > 0x80) // 是汉字
// {
// str_total_width += CHAR_WIDTH; // 一个汉字占 size 宽度
// p_tmp += 2; // 汉字2字节
// }
// else
// {
// p_tmp++; // 非汉字跳过
// }
// }
//
// switch (align)
// {
// case LCD_ALIGN_CENTER:
// curr_x = (x - str_total_width) / 2;
// break;
//
// case LCD_ALIGN_RIGHT:
// curr_x = x - str_total_width;
// break;
//
// case LCD_ALIGN_LEFT:
// default:
// curr_x = 0;
// break;
// }
//
// // ==========================
// // 正确显示一次整串
// // ==========================
// LCD_ShowChineseString(curr_x, y, (uint8_t *)p, fc, bc, size, mode);
//}
/******************************************************************************
[x1, x2]
x1 X坐标
x2 X坐标
y Y坐标
p
align LCD_ALIGN_LEFT / CENTER / RIGHT
fc
bc
mode
size 16
******************************************************************************/
uint32_t LCD_ShowChinese_AutoAlign(uint16_t x1, uint16_t x2, uint16_t y, const uint8_t *p,
uint8_t align, uint16_t fc, uint16_t bc, uint8_t mode, uint8_t size)
{
uint8_t CHAR_WIDTH = size;
uint16_t str_total_width = 0;
uint16_t curr_x = 0;
const uint8_t *p_tmp = p;
uint16_t area_width = x2 - x1 + 1; // 区域总宽度
// 计算汉字串总宽度
while (*p_tmp != '\0')
{
if ((uint8_t)*p_tmp > 0x80) // 汉字
{
str_total_width += CHAR_WIDTH;
p_tmp += 2;
}
else
{
p_tmp++;
}
}
// 根据对齐方式计算最终 X 坐标
switch (align)
{
case LCD_ALIGN_CENTER:
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
curr_x = x1;
break;
}
// 显示
LCD_ShowChineseString(curr_x, y, (uint8_t *)p, fc, bc, size, mode);
return curr_x;
}
/******************************************************************************
[x1, x2]
x1 X
x2 X
y Y
p
align //
bc 使
mode
size 16
LCD_ShowChinese_AutoAlign
******************************************************************************/
void LCD_ClearChinese_AutoAlign(uint16_t x1, uint16_t x2, uint16_t y, const uint8_t *p,
uint8_t align, uint8_t fc,uint16_t bc, uint8_t mode, uint8_t size)
{
uint8_t CHAR_WIDTH = size; // 汉字宽度 = 字体尺寸
uint16_t str_total_width = 0;
uint16_t curr_x = 0;
const uint8_t *p_tmp = p;
uint16_t area_width = x2 - x1 + 1; // 显示区间总宽度
// 1. 计算汉字字符串总宽度
while (*p_tmp != '\0')
{
if ((uint8_t)*p_tmp > 0x80) // 判断为汉字2字节
{
str_total_width += CHAR_WIDTH;
p_tmp += 2;
}
else
{
p_tmp++;
}
}
if (str_total_width == 0) return;
// 2. 根据对齐方式,计算汉字实际显示的起始 X 坐标
switch (align)
{
case LCD_ALIGN_CENTER:
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
curr_x = x1;
break;
}
// 3. 用背景色填充整块汉字区域,精准清除
LCD_FillByColor(curr_x, y, curr_x + str_total_width - 1, y + size - 1, bc);
}
/******************************************************************************
[x1, x2] ASCII /
x1 X坐标
x2 X坐标
y Y坐标
p
align LCD_ALIGN_LEFT / CENTER / RIGHT
fc
bc
mode
size 16=8*1624=12*24
******************************************************************************/
uint16_t LCD_ShowString_AutoAlign(uint16_t x1, uint16_t x2, uint16_t y, const uint8_t *p,
uint8_t align, uint16_t fc, uint16_t bc, uint8_t mode, uint8_t size)
{
uint8_t CHAR_WIDTH, CHAR_HEIGHT;
if (!LCD_GetAsciiMetrics(size, &CHAR_WIDTH, &CHAR_HEIGHT))
return 0;
uint16_t str_len = 0;
uint16_t str_total_width;
uint16_t curr_x = 0;
uint16_t area_width = x2 - x1 + 1; // 区间总宽度
// 计算字符串长度
while (p[str_len] != '\0')
{
str_len++;
}
// 计算字符串总宽度
str_total_width = str_len * CHAR_WIDTH;
// 根据对齐方式计算起始 X 坐标
switch (align)
{
case LCD_ALIGN_CENTER:
// 在区间内居中
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
// 在区间内靠右
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
// 在区间内靠左
curr_x = x1;
break;
}
// 显示字符串
while (*p != '\0')
{
LCD_ShowChar(curr_x, y, *p, fc, bc, CHAR_HEIGHT, mode);
curr_x += CHAR_WIDTH;
p++;
}
return curr_x;
}
/******************************************************************************
[x1, x2] ASCII /
x1 X
x2 X
y Y
p
align //
bc 使
mode
size 16/24
使
******************************************************************************/
void LCD_ClearString_AutoAlign(uint16_t x1, uint16_t x2, uint16_t y, const uint8_t *p,
uint8_t align, uint16_t fc,uint16_t bc, uint8_t mode, uint8_t size)
{
uint8_t CHAR_WIDTH, CHAR_HEIGHT;
// 根据字体尺寸,设置字符宽度和高度
if(size == 16)
{
CHAR_WIDTH = 8; // 8*16 字体
CHAR_HEIGHT = 16;
}
else if(size == 24)
{
CHAR_WIDTH = 12; // 12*24 字体
CHAR_HEIGHT = 24;
}
else
{
return; // 不支持的字体
}
uint16_t str_len = 0;
uint16_t str_total_width;
uint16_t curr_x = 0;
uint16_t area_width = x2 - x1 + 1; // 显示区间总宽度
// 1. 计算字符串字符个数
while (p[str_len] != '\0')
{
str_len++;
}
if (str_len == 0) return; // 空串直接返回
// 2. 计算字符串整体占用宽度
str_total_width = str_len * CHAR_WIDTH;
// 3. 根据对齐方式,计算字符串实际显示的起始 X 坐标
switch (align)
{
case LCD_ALIGN_CENTER:
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
curr_x = x1;
break;
}
// 4. 用背景色填充整块字符串区域,实现干净清除
LCD_FillByColor(curr_x, y, curr_x + str_total_width - 1, y + CHAR_HEIGHT - 1, bc);
}
// ============================================================
// 带垂直渐变的汉字显示(先画渐变背景,再叠加显示文字)
// 入口数据x1,x2 水平区间
// y 顶部Y坐标
// p 汉字串
// align 对齐方式
// fc 文字颜色
// top/bot 渐变顶部/底部颜色
// size 汉字尺寸16
// ============================================================
uint32_t LCD_ShowChinese_AutoAlign_Gradient(uint16_t x1, uint16_t x2, uint16_t y,
const uint8_t *p, uint8_t align, uint16_t fc,
uint16_t color_top, uint16_t color_bottom, uint8_t size)
{
uint8_t CHAR_WIDTH = size;
uint16_t str_total_width = 0;
uint16_t curr_x = 0;
const uint8_t *p_tmp = p;
uint16_t area_width = x2 - x1 + 1;
// 计算汉字串总宽度
while (*p_tmp != '\0')
{
if ((uint8_t)*p_tmp > 0x80)
{
str_total_width += CHAR_WIDTH;
p_tmp += 2;
}
else
{
p_tmp++;
}
}
if (str_total_width == 0) return 0;
// 根据对齐方式计算起始X
switch (align)
{
case LCD_ALIGN_CENTER:
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
curr_x = x1;
break;
}
// 1. 画垂直渐变背景
LCD_FillGradient(curr_x, y, curr_x + str_total_width, y + size, color_top, color_bottom);
// 2. 叠加模式显示文字mode=1只画笔画不画背景
LCD_ShowChinese_AutoAlign(x1, x2, y, p, align, fc, 0, 1, size);
return curr_x;
}
// ============================================================
// 带垂直渐变的 ASCII 字符串显示
// ============================================================
uint16_t LCD_ShowString_AutoAlign_Gradient(uint16_t x1, uint16_t x2, uint16_t y,
const uint8_t *p, uint8_t align, uint16_t fc,
uint16_t color_top, uint16_t color_bottom, uint8_t size)
{
uint8_t CHAR_WIDTH, CHAR_HEIGHT;
if (!LCD_GetAsciiMetrics(size, &CHAR_WIDTH, &CHAR_HEIGHT))
return 0;
uint16_t str_len = 0;
uint16_t str_total_width;
uint16_t curr_x = 0;
uint16_t area_width = x2 - x1 + 1;
while (p[str_len] != '\0') str_len++;
if (str_len == 0) return 0;
str_total_width = str_len * CHAR_WIDTH;
switch (align)
{
case LCD_ALIGN_CENTER:
curr_x = x1 + (area_width - str_total_width) / 2;
break;
case LCD_ALIGN_RIGHT:
curr_x = x2 - str_total_width + 1;
break;
case LCD_ALIGN_LEFT:
default:
curr_x = x1;
break;
}
// 1. 画垂直渐变背景
LCD_FillGradient(curr_x, y, curr_x + str_total_width, y + CHAR_HEIGHT, color_top, color_bottom);
// 2. 叠加模式显示文字mode=1
LCD_ShowString_AutoAlign(x1, x2, y, p, align, fc, 0, 1, size);
return curr_x;
}
/******************************************************************************
+ bg_color /
******************************************************************************/
void LCD_DrawArrow_Left(uint16_t x, uint16_t y, uint8_t size, uint16_t color, uint16_t bg_color)
{
int16_t box_side = size * 2 + 5;
int16_t box_x = x - box_side / 2;
int16_t box_y = y - box_side / 2;
LCD_FillRoundRect(box_x, box_y, box_side, box_side, 3, bg_color);
int16_t i;
for(i = 0; i < size; i++)
{
LCD_DrawLine(
x - i,
y - size + i,
x - i,
y + size - i,
color
);
}
}
void LCD_DrawArrow_Right(uint16_t x, uint16_t y, uint8_t size, uint16_t color, uint16_t bg_color)
{
int16_t box_side = size * 2 + 5;
int16_t box_x = x - box_side / 2;
int16_t box_y = y - box_side / 2;
LCD_FillRoundRect(box_x, box_y, box_side, box_side, 3, bg_color);
int16_t i;
for(i = 0; i < size; i++)
{
LCD_DrawLine(
x + i,
y - size + i,
x + i,
y + size - i,
color
);
}
}
// ============================================================
// 绘制向右的空心尖括号箭头(无填充、无底边、无背景框)
// 等边三角形,只保留指向右侧的两条斜边
// 入口数据x,y 箭头尖端坐标(尖角处)
// size 箭头大小(边长,越大箭头越宽)
// thick 线条粗细像素数1=单线2=加粗...
// color 颜色
// ============================================================
void LCD_DrawChevron_Right(uint16_t x, uint16_t y, uint8_t size, uint8_t thick, uint16_t color)
{
// 上斜线:从左下 (x, y-size) 向右上汇聚到 (x+size, y)thick条平行线
for (uint8_t i = 0; i < thick; i++) {
LCD_DrawLine(x, y - size + i, x + size - i, y, color);
}
// 下斜线:从左上 (x, y+size) 向右下汇聚到 (x+size, y)thick条平行线
for (uint8_t i = 0; i < thick; i++) {
LCD_DrawLine(x, y + size - i, x + size - i, y, color);
}
}
/******************************************************************************
+ +
x, y
size
radius 3~5
fg_color
bg_color
******************************************************************************/
void LCD_DrawPlus_RoundBox(uint16_t x, uint16_t y, uint8_t size, uint8_t radius, uint16_t fg_color, uint16_t bg_color)
{
int16_t i;
uint8_t box_w = size; // 框宽度
uint8_t box_h = size; // 框高度
uint8_t line_w = 4; // 线条粗细
LCD_FillRoundRect(x - box_w/2, y - box_h/2, box_w, box_h, radius, bg_color);
// 2. 画水平居中横线
for(i = 0; i < line_w; i++)
{
LCD_DrawLine(
x - box_w/3, y - line_w/2 + i,
x + box_w/3, y - line_w/2 + i,
fg_color
);
}
// 3. 画垂直居中竖线
for(i = 0; i < line_w; i++)
{
LCD_DrawLine(
x - line_w/2 + i, y - box_h/3,
x - line_w/2 + i, y + box_h/3,
fg_color
);
}
}
/******************************************************************************
+ -
x, y
size
radius
fg_color
bg_color
******************************************************************************/
void LCD_DrawMinus_RoundBox(uint16_t x, uint16_t y, uint8_t size, uint8_t radius, uint16_t fg_color, uint16_t bg_color)
{
int16_t i;
uint8_t box_w = size;
uint8_t box_h = size;
uint8_t line_w = 2;
LCD_FillRoundRect(x - box_w/2, y - box_h/2, box_w, box_h, radius, bg_color);
// 2. 画水平居中横线
for(i = 0; i < line_w; i++)
{
LCD_DrawLine(
x - box_w/3, y - line_w/2 + i,
x + box_w/3, y - line_w/2 + i,
fg_color
);
}
}
/**
* @brief
*/
static void LCD_FillRoundTopRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, uint16_t color)
{
if(w == 0 || h == 0)
{
return;
}
if(r > (w / 2))
{
r = w / 2;
}
if(r > h)
{
r = h;
}
if(r == 0)
{
LCD_FillByColor(x, y, x + w, y + h, color);
return;
}
LCD_FillRoundRect(x, y, w, h, r, color);
if(h > r)
{
LCD_FillByColor(x, y + h - r, x + w, y + h, color);
}
}
/**
* @brief RGB565
*/
static uint16_t LCD_BlendColor565(uint16_t c0, uint16_t c1, uint8_t num, uint8_t den)
{
uint16_t r0 = (c0 >> 11) & 0x1F;
uint16_t g0 = (c0 >> 5) & 0x3F;
uint16_t b0 = c0 & 0x1F;
uint16_t r1 = (c1 >> 11) & 0x1F;
uint16_t g1 = (c1 >> 5) & 0x3F;
uint16_t b1 = c1 & 0x1F;
uint16_t r;
uint16_t g;
uint16_t b;
if(den == 0)
{
return c1;
}
r = r0 + (uint16_t)(r1 - r0) * num / den;
g = g0 + (uint16_t)(g1 - g0) * num / den;
b = b0 + (uint16_t)(b1 - b0) * num / den;
return (r << 11) | (g << 5) | b;
}
/**
* @brief 5
* @param volume_level: (0-50=5=)
* @note N N ()
*/
void Draw_Volume_Bar(uint16_t x, uint16_t y, uint8_t volume_level,uint8_t scale,uint16_t color_on, uint16_t color_off)
{
uint8_t i;
uint8_t bar_count = 5;
uint8_t w = 3 * scale;
uint8_t gap = 2 * scale;
uint8_t radius = 1 * scale;
uint8_t height[5] = {6, 9, 12, 15, 18};
uint8_t max_h = height[4];
uint16_t clear_w;
uint16_t dim_color = color_off;
(void)color_on;
if(volume_level > 5)
{
volume_level = 5;
}
clear_w = (uint16_t)(w + gap) * bar_count - gap;
LCD_FillByColor(x, y, x + clear_w, y + max_h * scale, BLACK);
if(volume_level == 0)
{
return;
}
for(i = 0; i < bar_count; i++)
{
uint16_t curr_x = x + (w + gap) * i;
uint8_t curr_h = height[i] * scale;
uint8_t curr_y = y + (max_h * scale - curr_h);
uint16_t bar_color;
if(i >= volume_level)
{
continue;
}
if(volume_level == 1)
{
bar_color = WHITE;
}
else
{
/* 左暗右亮第1格最暗当前档位的最右格为全白 */
bar_color = LCD_BlendColor565(dim_color, WHITE, i, volume_level - 1);
}
LCD_FillRoundTopRect(curr_x, curr_y, w, curr_h, radius, bar_color);
}
}
///**
// * @brief 绘制电池图标+电量百分比
// * @param x/y: 电池图标左上角坐标, battery_percent: 电量(0-100)
// */
//void Draw_Battery_Icon(uint16_t x, uint16_t y, uint8_t battery_percent)
//{
//// LCD_FillByColor(x,y,x+25,y+20);
// LCD_FillRoundRect(x, y, 25, 15,2,WHITE);
// LCD_FillRoundRect(x+1, y+1, 23, 13,0,BLACK);
// LCD_FillRoundRect(x+1+1, y+1+1, 21, 11,0,WHITE);
//}
/**
* @brief 线 + +
*/
void Draw_Battery_Icon(uint16_t x, uint16_t y, uint8_t battery_percent,uint8_t scale, uint16_t border_color, uint16_t bg_color, uint16_t fill_color)
{
uint16_t bat_w = 24 * scale;
uint16_t bat_h = 11 * scale;
uint8_t round_r = 2 * scale;
uint8_t stroke = 1 * scale;
uint16_t fill_gap = 2 * scale;
/* PDF 标注 7.5,但 8px ASCII 位图乱码;用 16px 并整组右对齐,避免左右被裁 */
uint8_t text_size = 16 * scale;
uint8_t status_bar_h = 26 * scale;
uint8_t Info[40];
uint16_t pole_w = 2 * scale;
uint16_t pole_h = 5 * scale;
uint16_t gap = 6 * scale;
uint16_t right_margin = 14 * scale;
uint16_t pole_x;
uint16_t pole_y;
uint16_t inner_w;
uint16_t inner_h;
uint16_t fill_w;
uint16_t text_x;
uint16_t text_y;
uint16_t text_w;
uint16_t group_w;
uint16_t bat_x = x;
uint16_t clear_h;
if(battery_percent > 100)
{
battery_percent = 100;
}
sprintf((char*)Info, "%d%%", battery_percent);
text_w = (uint16_t)(strlen((char*)Info) * (text_size / 2));
group_w = (uint16_t)(bat_w + pole_w + gap + text_w);
if (group_w + right_margin < 240)
bat_x = (uint16_t)(240 - right_margin - group_w);
if (bat_x < x)
bat_x = x;
clear_h = (bat_h > text_size) ? bat_h : text_size;
if(status_bar_h > clear_h)
{
clear_h = status_bar_h;
}
LCD_FillByColor(bat_x > 4 ? (uint16_t)(bat_x - 4) : 0, 0, 240, clear_h, BLACK);
/* 圆角外壳:外框 + 内挖空 */
LCD_FillRoundRect(bat_x, y, bat_w, bat_h, round_r, border_color);
if(bat_w > 2 * stroke && bat_h > 2 * stroke)
{
LCD_FillRoundRect(bat_x + stroke, y + stroke,
bat_w - 2 * stroke, bat_h - 2 * stroke,
(round_r > stroke) ? (round_r - stroke) : 0,
bg_color);
}
inner_w = bat_w - 2 * fill_gap;
inner_h = bat_h - 2 * fill_gap;
fill_w = inner_w * battery_percent / 100;
if(battery_percent > 0 && fill_w > 0)
{
if(fill_w > inner_w)
{
fill_w = inner_w;
}
LCD_FillRoundRect(bat_x + fill_gap, y + fill_gap,
fill_w, inner_h,
1 * scale, fill_color);
}
pole_x = bat_x + bat_w;
pole_y = y + (bat_h - pole_h) / 2;
LCD_FillRoundRect(pole_x, pole_y, pole_w, pole_h, 1 * scale, border_color);
text_x = (uint16_t)(pole_x + pole_w + gap);
text_y = (status_bar_h - text_size) / 2;
if ((int16_t)text_y < 0)
text_y = 0;
LCD_ShowString(text_x, text_y, Info, WHITE, BLACK, text_size, 0);
}
/******************************************************************************
绿 +
x,y
scale 1=
ring_color GREEN/0x07E0
= 40*scale = 20*scale
4 线
******************************************************************************/
void Draw_Charging_Icon(uint16_t x, uint16_t y, uint8_t scale, uint16_t ring_color)
{
uint8_t outer_r = 40 * scale; // 外圈半径
uint8_t inner_r = 20 * scale; // 内圈半径(外半径 - 环粗)
// ===== 1. 绘制绿色空心圆环 =====
// 用 LCD_DrawCircle 逐圈绘制circle 本身是 1px 线宽)
// 从 inner_r 到 outer_r 画同心圆,形成粗环
for (uint8_t i = inner_r; i <= outer_r; i++)
{
LCD_DrawCircle(x, y, i, ring_color);
}
// ===== 2. 绘制居中的闪电标志3px 粗线) =====
uint8_t s = scale;
// 每段画 3 条平行线(偏移 -1, 0, +1形成 3px 粗的闪电
for (int8_t offset = -1; offset <= 1; offset++)
{
// 顶部 → 右中(斜线)
LCD_DrawLine(
x - 4*s + offset, y - 16*s,
x + 3*s + offset, y - 5*s,
ring_color
);
// 右中 → 左中横线Y方向偏移
LCD_DrawLine(
x + 3*s, y - 5*s + offset,
x - 3*s, y + 5*s + offset,
ring_color
);
// 左中 → 底部(斜线)
LCD_DrawLine(
x - 3*s + offset, y + 5*s,
x + 4*s + offset, y + 16*s,
ring_color
);
// 水平填充中段Y偏移
LCD_DrawLine(
x - 4*s, y + 5*s + offset,
x + 4*s, y + 5*s + offset,
ring_color
);
}
}
///******************************************************************************
// 函数说明:画【向左】实心三角箭头
// 入口数据x, y 箭头中心点坐标
// size 箭头大小(建议 5~15
// color 箭头颜色
// 返回值: 无
//******************************************************************************/
//void LCD_DrawArrow_Left(uint16_t x, uint16_t y, uint8_t size, uint16_t color)
//{
// int16_t i;
// // 逐行画实心左箭头
// for(i = 0; i < size; i++)
// {
// // 画水平线:越靠近中间,线越长 → 形成三角
// LCD_DrawLine(
// x - i, // X起点
// y - size + i, // Y起点
// x - i, // X终点
// y + size - i, // Y终点
// color
// );
// }
//}
//
///******************************************************************************
// 函数说明:画【向右】实心三角箭头
// 入口数据x, y 箭头中心点坐标
// size 箭头大小(建议 5~15
// color 箭头颜色
// 返回值: 无
//******************************************************************************/
//void LCD_DrawArrow_Right(uint16_t x, uint16_t y, uint8_t size, uint16_t color)
//{
// int16_t i;
// // 逐行画实心右箭头
// for(i = 0; i < size; i++)
// {
// LCD_DrawLine(
// x + i, // X起点
// y - size + i, // Y起点
// x + i, // X终点
// y + size - i, // Y终点
// color
// );
// }
//}