#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= 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 填充颜色 返回值: 无 ******************************************************************************/ // 鍦嗚鐭╁舰鍗曡壊濉厖锛堜繚鐣欏師鏈夎皟鐢ㄨ�呬笉鍙楀奖鍝嶏級 // 鏃х増瀹炵幇鍦ㄤ笅鏂规敞閲婂尯鍐呬繚鐣欙紝渚垮洖婊? 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; // 闄愬埗鍦嗚鏈�ぇ涓嶈秴杩囧崐瀹�崐楂? 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; // 娓愬彉琛屽~鍏咃細閫犺璁$畻璇ュ瞾棰滆壊鍜屽彲瑙�寮韫堬紙鑰冭檻鍦嗚锛? 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*16,24=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-5,0=静音,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 // ); // } //}