K1Guitar/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c

2007 lines
62 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "includes.h"
#include "Drv_ILI9341_Lcd_EnglishFont.h"
#include "Drv_ILI9341_Lcd_ChineseFont.h"
#include "w25q128.h"
#define PIC_BATCH_SIZE 1024
//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;
}
}
/******************************************************************************
函数说明从W25Q128外部Flash读取图片数据并显示
入口数据x,y 显示起始坐标
length 图片宽度(像素)
width 图片高度(像素)
flash_addr W25Q128中的起始地址
返回值: 无
注意: 图片数据格式为RGB565每像素2字节
******************************************************************************/
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;
}
}
/******************************************************************************
函数说明:在指定区域填充颜色
入口数据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;
sizex = sizey / 2;
TypefaceNum = (sizex / 8 + ((sizex % 8) ? 1 : 0)) * sizey;
num = num - ' ';
// 一次性设置窗口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++)
{
if (sizey == 16) temp = ascii_1608[num][i];
else if (sizey == 13) temp = ascii_1306[num][i];
else if (sizey == 24) temp = ascii_2412[num][i];
else { LCD_DC_Clr(); return; }
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 // 叠加模式(保持逐点画,不改动)
{
for (i = 0; i < TypefaceNum; i++)
{
uint8_t temp;
if (sizey == 16) temp = ascii_1608[num][i];
else if (sizey == 13) temp = ascii_1306[num][i];
else if (sizey == 24) temp = ascii_2412[num][i];
else { LCD_DC_Clr(); return; }
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;
switch(sizey)
{
case 11:sizex = 11;TypefaceNum = 22;break;
case 12:sizex = 12;TypefaceNum = 24;break;
case 13:sizex = 13;TypefaceNum = 26;break;
case 16:sizex = 16;TypefaceNum = 32;break;
case 24:sizex = 24;TypefaceNum = 72;break;
default: return;break;
}
// 一次性设置窗口
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++)
{
switch(sizey)
{
case 11:temp = Chinese_font_11[index][i];break;
case 12:temp = Chinese_font_12[index][i];break;
case 13:temp = Chinese_font_13[index][i];break;
case 16:temp = Chinese_font_16[index][i];break;
case 24:temp = Chinese_font_24[index][i];break;
}
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 // 叠加模式(保持逐点画)
{
for (i = 0; i < TypefaceNum; i++)
{
uint8_t temp;
switch(sizey)
{
case 11:temp = Chinese_font_11[index][i];break;
case 12:temp = Chinese_font_12[index][i];break;
case 13:temp = Chinese_font_13[index][i];break;
case 16:temp = Chinese_font_16[index][i];break;
case 24:temp = Chinese_font_24[index][i];break;
}
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];// 临时存储2字节的缓冲区
const char (*codeTab)[2] = NULL; // 指向字库表的指针每个条目2字节
uint8_t xStep = 0;// 字符水平步进距离
switch (sizey)// 根据字体大小选择对应的字库和步进
{
case 11:
codeTab = CHS11Table;
xStep = 11;
break;
case 12:
codeTab = CHS12Table;
xStep = 12;
break;
case 13:
codeTab = CHS13Table;
xStep = 13;
break;
case 16:
codeTab = CHS16Table;
xStep = 16;
break;
case 24:
codeTab = CHS24Table;
xStep = 24;
break;
default:
return;
}
for (i = 0; i < slen; i++)// 遍历字符串中的每个字节
{
memcpy(TmpS, str, 1); // 拷贝1个字节到临时缓冲区
if ((uint8_t)TmpS[0] > 0x80) // 检查是否为中文字符的第一个字节(GBK编码中>0x80)
{
memcpy(TmpS, str, 2); // 拷贝完整的2字节中文字符
for (uint16_t j = 0; j < 1024; j++)// 在字库中查找该字符
{
// 比较两个字节是否匹配字库中的字符
if (TmpS[0] == codeTab[j][0] && TmpS[1] == codeTab[j][1])
{
//F 找到匹配字符,在屏幕上显示
LCD_ShowChinese(x, y, j, fc, bc, sizey, mode);
x += xStep; // x坐标向右移动一个字符宽度
str += 2; // 字符串指针前进2字节(跳过当前中文字符)
i++; // 中文字符占2字节,循环计数器额外+1
break; // 跳出字库查找循环
}
}
}
}
}
/******************************************************************************
函数说明:计算中英文混排字符串的像素宽度(静态辅助函数)
******************************************************************************/
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;
switch(sizey)
{
case 13: codeTab = CHS13Table; break;
case 12: codeTab = CHS12Table; break;
case 16: codeTab = CHS16Table; break;
case 24: codeTab = CHS24Table; break;
default: 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 = (sizey == 12) ? (sizeof(CHS12Table)/2) :
(sizey == 13) ? (sizeof(CHS13Table)/2) :
(sizey == 16) ? (sizeof(CHS16Table)/2) :
(sizey == 24) ? (sizeof(CHS24Table)/2) : 0;
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;
switch(size)
{
case 12:CHAR_WIDTH = 6; CHAR_HEIGHT = 12;break;
case 16:CHAR_WIDTH = 8; CHAR_HEIGHT = 16;break;
case 24:CHAR_WIDTH = 12; CHAR_HEIGHT = 24;break;
default :return 0; break;
}
// // 根据字体尺寸设置字符宽度
// if(size == 16)
// {
// CHAR_WIDTH = 8;
// CHAR_HEIGHT = 16;
// }
// else if(size == 24)
// {
// CHAR_WIDTH = 12;
// CHAR_HEIGHT = 24;
// }
// else
// {
// 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;
switch(size)
{
case 12:CHAR_WIDTH = 6; CHAR_HEIGHT = 12;break;
case 16:CHAR_WIDTH = 8; CHAR_HEIGHT = 16;break;
case 24:CHAR_WIDTH = 12; CHAR_HEIGHT = 24;break;
default :return 0; break;
}
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;
}
/******************************************************************************
// =====================================================================
// 旧版 LCD_DrawArrow_Left / LCD_DrawArrow_Right保留以备回滚
// 仅画箭头,无背景框
// =====================================================================
//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,
// 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)
//{
// int16_t i;
// for(i = 0; i < size; i++)
// {
// LCD_DrawLine(
// x + i,
// y - size + i,
// x + i,
// y + size - i,
// color
// );
// }
//}
// =====================================================================
// 新版 LCD_DrawArrow_Left / LCD_DrawArrow_Right
// 新增 bg_color 参数:圆角正方形背景框的颜色
// 背景框为正方形,边长 = 2*size + 8圆角半径 = 3
// 箭头居中显示在背景框内
// =====================================================================
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 + 8;
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 + 8;
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
);
}
}
// =====================================================================
// 新版 LCD_DrawArrow_Left / LCD_DrawArrow_Right
// 新增 bg_color 参数:圆角正方形背景框的颜色
// 背景框为正方形,边长 = 2*size + 8圆角半径 = 3
// 箭头居中显示在背景框内
// =====================================================================
* */
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);
LCD_FillRoundRectGradient(box_x, box_y, box_side, box_side, 3,START_COLOR,END_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);
LCD_FillRoundRectGradient(box_x, box_y, box_side, box_side, 3,START_COLOR,END_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; // 线条粗细
// 1. 画圆角填充背景框
// LCD_FillRoundRect(
// x - box_w/2, // 左
// y - box_h/2, // 上
// box_w, // 宽
// box_h, // 高
// radius, // 圆角
// bg_color // 背景颜色(灰色)
// );
LCD_FillRoundRectGradient(x - box_w/2, y - box_h/2, box_w, box_h, radius,START_COLOR,END_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;
// 1. 画圆角填充背景框
// LCD_FillRoundRect(
// x - box_w/2,
// y - box_h/2,
// box_w,
// box_h,
// radius,
// bg_color
// );
LCD_FillRoundRectGradient(x - box_w/2, y - box_h/2, box_w, box_h, radius,START_COLOR,END_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;
uint8_t text_size = 12 * scale;
uint8_t Info[40];
uint16_t pole_w = 2 * scale;
uint16_t pole_h = 5 * 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 clear_h;
if(battery_percent > 100)
{
battery_percent = 100;
}
clear_h = (bat_h > text_size) ? bat_h : text_size;
LCD_FillByColor(x, y, 240, y + clear_h + 2 * scale, BLACK);
/* 圆角外壳:外框 + 内挖空 */
LCD_FillRoundRect(x, y, bat_w, bat_h, round_r, border_color);
if(bat_w > 2 * stroke && bat_h > 2 * stroke)
{
LCD_FillRoundRect(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(x + fill_gap, y + fill_gap,
fill_w, inner_h,
1 * scale, fill_color);
}
/* 右侧正极小凸起 */
pole_x = 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);
sprintf((char*)Info, "%d%%", battery_percent);
text_x = pole_x + pole_w + 2 * scale;
text_y = y + (bat_h - text_size) / 2;
LCD_ShowString(text_x, text_y, Info, WHITE, bg_color, 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
// );
// }
//}