重构代码的架构设计,增加测试单元,提高代码可靠性
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#ifndef __DISPLAY__H__
|
||||
#define __DISPLAY__H__
|
||||
|
||||
#include "../../include/types.h"
|
||||
#include "types.h"
|
||||
|
||||
|
||||
/* 静态菜定义需要的属性 */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef __KEY_H__
|
||||
#define __KEY_H__
|
||||
|
||||
#include "../../include/types.h"
|
||||
#include "types.h"
|
||||
|
||||
|
||||
|
||||
|
||||
602
src/Drv/lcd.c
602
src/Drv/lcd.c
@@ -1,602 +0,0 @@
|
||||
|
||||
#include "lcd.h"
|
||||
#include <string.h>
|
||||
#include "ascii.h"
|
||||
#include <math.h>
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
tagScreenControl g_tCVsr; // 当前界面结构指针
|
||||
tagDspAttrib g_tDspAttrib; // 显示属性
|
||||
|
||||
void Lcd_Init(void)
|
||||
{
|
||||
/* 清空显存 */
|
||||
memset(g_tCVsr.pwbyLCDMemory, 0, sizeof(g_tCVsr.pwbyLCDMemory));
|
||||
/* 设置默认颜色 */
|
||||
g_tCVsr.wFontColor = LCD_COLOR_WHITE;
|
||||
g_tCVsr.wBackColor = LCD_COLOR_BLACK;
|
||||
/* 设置默认字体 */
|
||||
//g_tCVsr.pwLibHZ = (uint16_t*)HZK12;
|
||||
/* 字体的大小需要调试 */
|
||||
g_tCVsr.wGBFontWidth = 13;
|
||||
g_tCVsr.wGBFontHeight = 12;
|
||||
|
||||
/* 设置默认ASCII字体 */
|
||||
g_tCVsr.pbyLibAscii = g_abyASCII126[0];
|
||||
g_tCVsr.wASCIIFontWidth = 7;
|
||||
g_tCVsr.wASCIIFontHeight = 12;
|
||||
g_tDspAttrib.rowSpace = 2;
|
||||
}
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color)
|
||||
{
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* 一个字节一个像素点 */
|
||||
g_tCVsr.pwbyLCDMemory[y * LCD_LINE_SIZE + x] = color;
|
||||
}
|
||||
|
||||
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 一个字节一个像素点 */
|
||||
return g_tCVsr.pwbyLCDMemory[y * LCD_LINE_SIZE + x];
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_FillRect
|
||||
* 功能: 在显存中用指定颜色填充一个矩形区域
|
||||
* 参数: lx - 左上角 X 坐标 (left x)
|
||||
* ty - 左上角 Y 坐标 (top y)
|
||||
* rx - 右下角 X 坐标 (right x)
|
||||
* by - 右下角 Y 坐标 (bottom y)
|
||||
* color- 填充颜色
|
||||
* 返回: 无
|
||||
* 说明: 对 [left_x, right_x] × [top_y, bottom_y] 区域内的每个像素逐点调用 Lcd_SetPixel 进行填充,
|
||||
* 仅操作显存,不负责刷屏输出,由上层根据需要统一刷新。
|
||||
*****************************************************************************/
|
||||
void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color)
|
||||
{
|
||||
for(uint16_t y = top_y; y <= bottom_y; y++ )
|
||||
{
|
||||
for(uint16_t x = left_x; x <= right_x; x++ )
|
||||
{
|
||||
Lcd_SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 宏名: M_GuiSwap
|
||||
* 功能: 交换两个整型变量的值(按位异或方式)
|
||||
* 说明: 要求 a、b 为同一类型的可写左值,且 a、b 不能是同一地址
|
||||
*****************************************************************************/
|
||||
#define M_GuiSwap(a, b) { a^=b; b^=a; a^=b; }
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_LineH
|
||||
* 功能: 在显存中绘制一条水平实线(可指定线宽)
|
||||
* 参数: wXStart - 线段起始 X 坐标(左端)
|
||||
* wXEnd - 线段结束 X 坐标(右端,若小于起始则自动交换)
|
||||
* wYStart - 线段上边缘 Y 坐标
|
||||
* wWidth - 线宽(沿 Y 方向的像素高度)
|
||||
* 返回: 无
|
||||
* 说明: 1. 若 wXEnd < wXStart,会先交换,保证从左向右绘制
|
||||
* 2. 实际绘制区域为 [wXStart, wXEnd) × [wYStart, wYStart + wWidth)
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wYEnd = wYStart + wWidth; /* 计算水平线在 Y 方向的结束位置 = 起始 Y + 线宽 */
|
||||
|
||||
if( wXEnd < wXStart ) /* 若结束 X 小于起始 X,说明调用参数顺序反了,需要交换 */
|
||||
{
|
||||
M_GuiSwap(wXEnd, wXStart); /* 使用异或交换宏,将 wXStart 与 wXEnd 的值互换 */
|
||||
}
|
||||
for(uint16_t x = wXStart; x < wXEnd; x++ ) /* 从左到右,遍历线段覆盖的每一列 X 坐标 */
|
||||
{
|
||||
for(uint16_t y = wYStart; y < wYEnd; y++ ) /* 在当前 X 列内,从上到下按线宽遍历每个像素行 */
|
||||
{
|
||||
Lcd_SetPixel(x, y, color); /* 将当前像素点设置为当前字体颜色,实现实心线绘制 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_LineV
|
||||
* 功能: 在显存中绘制一条垂直实线(可指定线宽)
|
||||
* 参数: wYStart - 线段起始 Y 坐标(上端)
|
||||
* wYEnd - 线段结束 Y 坐标(下端,若小于起始则自动交换)
|
||||
* wXStart - 线段左边缘 X 坐标
|
||||
* wWidth - 线宽(沿 X 方向的像素宽度)
|
||||
* 返回: 无
|
||||
* 说明: 1. 若 wYEnd < wYStart,会先交换,保证从上向下绘制
|
||||
* 2. 实际绘制区域为 [wXStart, wXStart + wWidth) × [wYStart, wYEnd)
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wXEnd = wXStart + wWidth;
|
||||
|
||||
if( wXEnd < wXStart ) /* 若结束 X 小于起始 X,说明调用参数顺序反了,需要交换 */
|
||||
{
|
||||
M_GuiSwap(wYEnd, wYStart); /* 使用异或交换宏,将 wYStart 与 wYEnd 的值互换 */
|
||||
}
|
||||
|
||||
for(uint16_t x = wXStart; x < wXEnd; x++) /* 从左到右,遍历线段覆盖的每一列 X 坐标 */
|
||||
{
|
||||
for(uint16_t y = wYStart; y < wYEnd; y++) /* 从上到下,遍历线段覆盖的每一行 Y 坐标 */
|
||||
{
|
||||
Lcd_SetPixel(x, y, color); /* 将当前像素点设置为当前字体颜色,实现实心线绘制 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_Line
|
||||
* 功能: 在显存中绘制一条任意斜率的直线,并支持指定线宽
|
||||
* 参数: wXstart - 起点 X 坐标
|
||||
* wYstart - 起点 Y 坐标
|
||||
* wXend - 终点 X 坐标
|
||||
* wYend - 终点 Y 坐标
|
||||
* wWidth - 线宽(像素),以线段中轴为中心向两侧扩展
|
||||
* 返回: 无
|
||||
* 说明: 1. 对水平线/垂直线分别调用 Lcd_LineH / Lcd_LineV 进行优化绘制
|
||||
* 2. 其它情况使用类 Bresenham 算法,从两端向中间对称绘制,并按线宽加粗
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wDX; /* X 方向差值(水平偏移量的绝对值) */
|
||||
uint16_t wDY; /* Y 方向差值(垂直偏移量的绝对值) */
|
||||
uint16_t wSignY; /* 纵向增量符号(+1 或 -1),决定向上/向下绘制 */
|
||||
uint16_t wSignX; /* 横向增量符号(+1 或 -1),决定向左/向右绘制 */
|
||||
uint16_t wDecision; /* 误差累积量,类似 Bresenham 算法中的判别值 */
|
||||
uint16_t wCurx, wCury, wNextx, wNexty, wPY, wPX; /* 当前端点、对称端点以及循环中使用的临时坐标 */
|
||||
|
||||
/* 若起点和终点 Y 相同,说明是水平直线,直接调用水平画线函数即可 */
|
||||
if( wYstart == wYend )
|
||||
{
|
||||
Lcd_LineH(wXstart, wXend, wYstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
/* 若起点和终点 X 相同,说明是垂直直线,直接调用垂直画线函数即可 */
|
||||
if( wXstart == wXend )
|
||||
{
|
||||
Lcd_LineV(wYstart, wYend, wXstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 计算水平和垂直方向的距离,后续根据 wDX / wDY 决定主增量方向 */
|
||||
wDX = abs(wXstart - wXend);
|
||||
wDY = abs(wYstart - wYend);
|
||||
/* 为了统一从“左到右 / 上到下”的绘制方向,对某些象限的线段进行起终点交换 */
|
||||
if (((wDX >= wDY && (wXstart > wXend)) || /* 以 X 为主增量,且起点在右侧,则交换 */
|
||||
((wDY > wDX) && (wYstart > wYend)))) /* 以 Y 为主增量,且起点在下侧,则交换 */
|
||||
{
|
||||
M_GuiSwap(wXend, wXstart); /* 交换 X 坐标,使起点在左/上 */
|
||||
M_GuiSwap(wYend, wYstart); /* 交换 Y 坐标,对应调整终点 */
|
||||
}
|
||||
/* 计算每一步在 Y 方向上的符号:向下(+1) 或向上(-1) */
|
||||
wSignY = (wYend - wYstart) / wDY;
|
||||
/* 计算每一步在 X 方向上的符号:向右(+1) 或向左(-1) */
|
||||
wSignX = (wXend - wXstart) / wDX;
|
||||
|
||||
/* 情况一:X 方向偏移大于等于 Y 方向(线更“横向”) */
|
||||
if (wDX >= wDY)
|
||||
{
|
||||
wCurx = wXstart; /* 当前点 X 从起点开始 */
|
||||
wCury = wYstart; /* 当前点 Y 从起点开始 */
|
||||
wNextx = wXend; /* 对称点 X 从终点开始(用于加粗两端) */
|
||||
wNexty = wYend; /* 对称点 Y 从终点开始 */
|
||||
wDecision = (wDX >> 1); /* 初始化判别值为一半的 wDX */
|
||||
for (; wCurx <= wNextx; ) /* 从两端向中间画,直到两个端点相遇或交叉 */
|
||||
{
|
||||
/* 累积的误差超过一条“格子宽度”时,说明需要在 Y 方向跨一格 */
|
||||
if (wDecision >= wDX)
|
||||
{
|
||||
wDecision -= wDX; /* 误差回退一个 wDX,保持在合理范围内 */
|
||||
wCury += wSignY; /* 当前端点 Y 沿着符号方向移动一格 */
|
||||
wNexty -= wSignY; /* 对称端点 Y 反向移动一格,保持对称 */
|
||||
}
|
||||
/* 以当前端点 (wCurx, wCury) 为中心,按线宽在 Y 方向“扩粗”填充像素 */
|
||||
for (wPY = wCury - wWidth / 2; wPY <= wCury + wWidth / 2; wPY++)
|
||||
{
|
||||
Lcd_SetPixel(wCurx, wPY, color);
|
||||
}
|
||||
|
||||
/* 以对称端点 (wNextx, wNexty) 为中心,同样按线宽在 Y 方向填充,实现两端对称绘制 */
|
||||
for (wPY = wNexty - wWidth / 2; wPY <= wNexty + wWidth / 2; wPY++)
|
||||
{
|
||||
Lcd_SetPixel(wNextx, wPY, color);
|
||||
}
|
||||
wCurx++; /* 当前端点 X 向右移动一格 */
|
||||
wNextx--; /* 对称端点 X 向左移动一格 */
|
||||
wDecision += wDY; /* 增加误差值,下一轮判断是否需要在 Y 方向跨格 */
|
||||
}
|
||||
}
|
||||
/* 情况二:Y 方向偏移大于 X 方向(线更“竖向”) */
|
||||
else
|
||||
{
|
||||
wCurx = wXstart; /* 当前点 X 从起点开始 */
|
||||
wCury = wYstart; /* 当前点 Y 从起点开始 */
|
||||
wNextx = wXend; /* 对称点 X 从终点开始 */
|
||||
wNexty = wYend; /* 对称点 Y 从终点开始 */
|
||||
wDecision = (wDY >> 1); /* 初始化判别值为一半的 wDY */
|
||||
for (; wCury <= wNexty; ) /* 从两端向中间画,直到在 Y 方向相遇 */
|
||||
{
|
||||
/* 累积误差超过一条“格子高度”时,说明需要在 X 方向跨一格 */
|
||||
if (wDecision >= wDY)
|
||||
{
|
||||
wDecision -= wDY; /* 回退一个 wDY,避免误差过大 */
|
||||
wCurx += wSignX; /* 当前端点 X 沿符号方向移动一格 */
|
||||
wNextx -= wSignX; /* 对称端点 X 反向移动一格 */
|
||||
}
|
||||
/* 以当前端点 (wCurx, wCury) 为中心,在 X 方向按线宽“扩粗”填充像素 */
|
||||
for (wPX = wCurx - wWidth / 2; wPX <= wCurx + wWidth / 2; wPX++)
|
||||
{
|
||||
Lcd_SetPixel(wPX, wCury, color);
|
||||
}
|
||||
|
||||
/* 以对称端点 (wNextx, wNexty) 为中心,同样在 X 方向扩粗填充,实现两端对称绘制 */
|
||||
for (wPX = wNextx - wWidth / 2; wPX <= wNextx + wWidth / 2; wPX++)
|
||||
{
|
||||
Lcd_SetPixel(wPX, wNexty, color);
|
||||
}
|
||||
wCury++; /* 当前端点 Y 向下移动一格 */
|
||||
wNexty--; /* 对称端点 Y 向上移动一格 */
|
||||
wDecision += wDX; /* 增加误差值,下一轮判断是否需要在 X 方向跨格 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_MeiTouPic
|
||||
* 功能: 在指定 Y 位置绘制一条带左右“眉头”装饰的水平线(中间直线 + 两端斜线)
|
||||
* 参数: wYStart - 中间水平线的 Y 坐标
|
||||
* wWidth - 线宽(像素),传递给 Lcd_LineH / Lcd_Line
|
||||
* 返回: 无
|
||||
* 说明: 1. 中间部分为 X=16..144 的水平粗线
|
||||
* 2. 左端在 (16, wYStart) 位置向左上方连一条斜线到 (8, wYStart-8)
|
||||
* 3. 右端在 (144, wYStart) 位置向右上方连一条斜线到 (152, wYStart-8)
|
||||
*****************************************************************************/
|
||||
void Lcd_MeiTouPic(uint16_t wYStart, uint16_t wWidth)
|
||||
{
|
||||
Lcd_LineH(16, 144, wYStart, wWidth, g_tCVsr.wFontColor); /* 中间水平粗线段 */
|
||||
Lcd_Line(8, wYStart - 8, 16, wYStart, wWidth, g_tCVsr.wFontColor);/* 左端向上的斜线“眉头” */
|
||||
Lcd_Line(144, wYStart, 152, wYStart - 8, wWidth, g_tCVsr.wFontColor);/* 右端向上的斜线“眉头” */
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Lcd_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y)
|
||||
{
|
||||
Lcd_LineH(left_x, right_x,top_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y,left_x, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineH(left_x, right_x,bottom_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y,right_x, 1, g_tCVsr.wFontColor);
|
||||
}
|
||||
/******************************************************************************
|
||||
* 宏名: M_Max / M_Min
|
||||
* 功能: 返回两个数中的较大值 / 较小值(简单三目运算宏)
|
||||
*****************************************************************************/
|
||||
#define M_Max( a, b ) ( ((a) > (b)) ? (a) : (b) )
|
||||
#define M_Min( a, b ) ( ((a) < (b)) ? (a) : (b) )
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_Invert
|
||||
* 功能: 对指定矩形区域内的像素进行“反相”操作(黑变白、白变黑)
|
||||
* 参数: wXstart - 矩形左上角 X 坐标
|
||||
* wYstart - 矩形左上角 Y 坐标
|
||||
* wXend - 矩形右下角 X 坐标
|
||||
* wYend - 矩形右下角 Y 坐标
|
||||
* 返回: 无
|
||||
* 说明: 1. 首先进行边界检查,防止坐标越界访问显存
|
||||
* 2. 使用 M_Min/M_Max 规范化矩形对角坐标为 (xMin,yMin)-(xMax,yMax)
|
||||
* 3. 遍历矩形区域,每个像素值按位取反写回,实现反白/反显效果
|
||||
*****************************************************************************/
|
||||
void Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend)
|
||||
{
|
||||
uint16_t xMin, xMax, yMin, yMax; /* 归一化后的矩形边界坐标 */
|
||||
uint8_t wColor; /* 当前像素原始色值(8bit 单色) */
|
||||
|
||||
/* 边界检查:若任意一端 X 超出屏幕宽度,则直接返回,不做处理 */
|
||||
if ((wXstart > LCD_SIZE_X) || (wXend > LCD_SIZE_X))
|
||||
return;
|
||||
/* 边界检查:若任意一端 Y 超出屏幕高度,则直接返回 */
|
||||
if ((wYstart > LCD_SIZE_Y) || (wYend > LCD_SIZE_Y))
|
||||
return;
|
||||
|
||||
/* 规范化矩形:xMin/xMax 为左右边界,yMin/yMax 为上下边界(不关心调用时 start/end 的大小关系) */
|
||||
xMin = M_Min(wXstart, wXend);
|
||||
yMin = M_Min(wYstart, wYend);
|
||||
xMax = M_Max(wXstart, wXend);
|
||||
yMax = M_Max(wYstart, wYend);
|
||||
|
||||
/* 双重循环遍历矩形区域内的每个像素点(不包含 xMax/yMax 边界本身) */
|
||||
for (uint16_t y = yMin; y < yMax; y++)
|
||||
{
|
||||
for (uint16_t x = xMin; x < xMax; x++)
|
||||
{
|
||||
/* 从显存中读取当前像素值,逐位取反后写回,实现黑白反转/反显效果 */
|
||||
wColor = g_tCVsr.pwbyLCDMemory[y * LCD_SIZE_X + x];
|
||||
g_tCVsr.pwbyLCDMemory[y * LCD_SIZE_X + x] = (uint8_t)~wColor;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
//==============================================================================
|
||||
// 功能说明 : 在指定屏幕坐标处显示一个 ASCII 字符
|
||||
// 设计说明 : 从 ASCII 字库中取出点阵, 按当前显示属性(正显/反显、旋转 90° 与否)
|
||||
// 逐点调用画点函数进行显示
|
||||
// 参数说明 : byScreen - 屏幕号
|
||||
// x, y - 字符左上角基准坐标(若 Rotate!=0, 实际显示会旋转 90°)
|
||||
// byAscii - 要显示的 ASCII 码
|
||||
// bTR - TRUE : 透明显示, 点阵为 0 时保留原背景
|
||||
// FALSE : 非透明, 点阵为 0 时用背景色重绘
|
||||
// 返回说明 : 0 - 正常
|
||||
// -1 - Y 方向越界
|
||||
// -2 - X 方向越界
|
||||
//==============================================================================
|
||||
inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||
{
|
||||
uint8_t i, j;
|
||||
uint8_t byLine, *pbyFontLib;
|
||||
uint8_t on_color, off_color;
|
||||
|
||||
/* 从 ASCII 字库中取得当前字符的点阵数据首地址
|
||||
每个字符占用 wASCIIFontHeight 个字节, 按行存储 */
|
||||
pbyFontLib = &g_tCVsr.pbyLibAscii[byAscii * g_tCVsr.wASCIIFontHeight];
|
||||
|
||||
/* 边界检查:根据旋转与字符宽高判断是否越界 */
|
||||
if (0 == g_tDspAttrib.Rotate) {
|
||||
if ((x + g_tCVsr.wASCIIFontWidth) > LCD_SIZE_X) return (uint16_t)-2; /* X 越界 */
|
||||
if ((y + g_tCVsr.wASCIIFontHeight) > LCD_SIZE_Y) return (uint16_t)-1; /* Y 越界 */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 旋转后宽高互换, 重新按宽高做边界检查 */
|
||||
if ((x + g_tCVsr.wASCIIFontHeight) > LCD_SIZE_X) return (uint16_t)-2; /* X 越界 */
|
||||
if ((y + 1) < g_tCVsr.wASCIIFontWidth) return (uint16_t)-1; /* Y 越界(向上旋转) */
|
||||
}
|
||||
|
||||
/* 根据正显/反显选择“点阵为 1/0 时用的颜色” */
|
||||
if (0 == g_tDspAttrib.style) {
|
||||
/* 正显:1 = 前景色,0 = 背景色 */
|
||||
on_color = g_tCVsr.wFontColor;
|
||||
off_color = g_tCVsr.wBackColor;
|
||||
} else {
|
||||
/* 反显:1 = 背景色,0 = 前景色 */
|
||||
on_color = g_tCVsr.wBackColor;
|
||||
off_color = g_tCVsr.wFontColor;
|
||||
}
|
||||
|
||||
for (j = 0; j < g_tCVsr.wASCIIFontHeight; j++)
|
||||
{
|
||||
byLine = pbyFontLib[j]; /* 第 j 行的 8bit 点阵 */
|
||||
|
||||
for (i = 0; i < g_tCVsr.wASCIIFontWidth; i++)
|
||||
{
|
||||
uint8_t bit_on = ((byLine << i) & 0x80) != 0;
|
||||
uint8_t color;
|
||||
uint16_t px, py;
|
||||
|
||||
if (bit_on)
|
||||
{
|
||||
color = on_color;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = off_color;
|
||||
}
|
||||
|
||||
if (0 == g_tDspAttrib.Rotate)
|
||||
{
|
||||
/* 不旋转: (x+i, y+j) */
|
||||
px = x + i;
|
||||
py = y + j;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 旋转 90°: 将 (i,j) 映射到 (x+j, y-i) */
|
||||
px = x + j;
|
||||
py = y - i;
|
||||
}
|
||||
Lcd_SetPixel(px, py, color);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 UTF-8 字节流中解析下一个字符的 Unicode 码点,并返回该字符占用的字节数。
|
||||
*
|
||||
* UTF-8 编码规则简要:
|
||||
* - 1 字节:0xxxxxxx(ASCII,0x00..0x7F)
|
||||
* - 2 字节:110xxxxx 10xxxxxx(U+0080..U+07FF)
|
||||
* - 3 字节:1110xxxx 10xxxxxx 10xxxxxx(U+0800..U+FFFF,含常用汉字)
|
||||
* - 4 字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(U+10000..,本函数不处理)
|
||||
*
|
||||
* @param utf8 指向当前 UTF-8 字节的指针(可含多字节序列)
|
||||
* @param out_unicode 输出该字符的 Unicode 码点(U+0000..U+FFFF)
|
||||
* @return 该字符占用的字节数 1/2/3;0 表示结束、无效或无法解析
|
||||
*/
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
|
||||
{
|
||||
unsigned char c = utf8[0];
|
||||
|
||||
/* 字符串结束:'\0' 不算一个可解析字符,返回 0 表示“无更多字符” */
|
||||
if (c == 0) {
|
||||
*out_unicode = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* 1 字节:ASCII(U+0000..U+007F)
|
||||
* 格式:0xxxxxxx,即 c < 0x80。码点等于该字节的数值。
|
||||
* ----------------------------------------------------------------------- */
|
||||
if (c < 0x80) {
|
||||
*out_unicode = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* 2 字节:U+0080..U+07FF(如拉丁扩展、希腊文等)
|
||||
* 格式:首字节 110xxxxx(0xC0..0xDF),次字节 10xxxxxx(0x80..0xBF)。
|
||||
* 码点 = (首字节低 5 位)<<6 | (次字节低 6 位) = (c&0x1F)<<6 | (utf8[1]&0x3F)。
|
||||
* ----------------------------------------------------------------------- */
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
if (utf8[1] == 0)
|
||||
return 0; /* 首字节后无后续字节,非法 UTF-8 序列 */
|
||||
*out_unicode = (uint32_t)((c & 0x1F) << 6 | (utf8[1] & 0x3F));
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* 3 字节:U+0800..U+FFFF(含常用汉字、日韩等)
|
||||
* 格式:首字节 1110xxxx(0xE0..0xEF),后两字节均为 10xxxxxx。
|
||||
* 码点 = (首字节低 4 位)<<12 | (第2字节低6位)<<6 | (第3字节低6位)。
|
||||
* 例如 “你” 的 UTF-8 为 E4 BD A0 → U+4F60。
|
||||
* ----------------------------------------------------------------------- */
|
||||
if ((c & 0xF0) == 0xE0) {
|
||||
if (utf8[1] == 0 || utf8[2] == 0)
|
||||
return 0; /* 缺少第二或第三字节,非法序列 */
|
||||
*out_unicode = (uint32_t)((c & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F));
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* 4 字节(U+10000 及以上)或非法首字节(如 10xxxxxx 单独出现):本实现不解析 */
|
||||
*out_unicode = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Lcd_Pub_UTF8(uint16_t x, uint16_t y, uint32_t unicode )
|
||||
{
|
||||
const uint8_t *bitmap = utf8_hz12_get(unicode);
|
||||
uint16_t word = 0;
|
||||
if (bitmap == NULL)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("Lcd_Pub_UTF8: unicode = %d, bitmap = NULL\n", unicode);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
for (uint8_t j = 0; j < g_tCVsr.wGBFontHeight; j++)
|
||||
{
|
||||
word = (uint16_t)((bitmap[j*2] << 8) | bitmap[j*2+1]);
|
||||
for (uint8_t i = 0; i < g_tCVsr.wGBFontWidth; i++)
|
||||
{
|
||||
if ((word >> (15 - i)) & 1)
|
||||
{
|
||||
Lcd_SetPixel(x + i, y + j, g_tCVsr.wFontColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lcd_SetPixel(x + i, y + j, g_tCVsr.wBackColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定坐标起逐字显示 UTF-8 字符串,支持汉字(UTF-8 字库)、ASCII 与换行。
|
||||
* 字符串以 \\0 结尾;遇换行符 0x0A 则换到下一行;本行放不下时擦除本行剩余部分。
|
||||
*
|
||||
* @param x, y 起始坐标(首个字符左上角)
|
||||
* @param pcString UTF-8 字符串
|
||||
* @return 0 成功;-1 起始 X 越界;-2 起始 Y 越界;<0 其它错误(如找不到换行导致换行失败)
|
||||
*/
|
||||
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
{
|
||||
uint16_t bakx, baky; /* 当前行行首坐标,换行时 x 回到 bakx */
|
||||
uint32_t unicode; /* utf8_next 解析出的当前字符码点 */
|
||||
uint16_t index = 0; /* 当前字符在 pcString 中的字节下标 */
|
||||
uint8_t n = 0;
|
||||
bakx = x;
|
||||
baky = y;
|
||||
|
||||
/* 起始坐标合法性:至少能放下一个 ASCII 宽、一行汉字高 */
|
||||
if (x > LCD_SIZE_X - g_tCVsr.wASCIIFontWidth)
|
||||
return -1;
|
||||
if (y >= LCD_SIZE_Y - g_tCVsr.wGBFontHeight)
|
||||
return -2;
|
||||
while (pcString[index] != 0x0)
|
||||
{
|
||||
/* 解析当前字符:n = 占用字节数(1=ASCII,2/3=多字节),unicode = 码点 */
|
||||
n = utf8_next(pcString + index, &unicode);
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
/* ---------- 多字节字符(如汉字):n=2 或 3,用 UTF-8 字库绘制 ---------- */
|
||||
if (n > 1)
|
||||
{
|
||||
/* 本行剩余宽度放不下一个汉字:向后查找换行符并换行 */
|
||||
if (x > LCD_SIZE_X - g_tCVsr.wGBFontWidth)
|
||||
{
|
||||
/*擦除本行剩余部分*/
|
||||
for(uint16_t j = y; j < y + g_tCVsr.wGBFontHeight; j++)
|
||||
{
|
||||
for(uint16_t i = x; i < LCD_SIZE_X; i++)
|
||||
{
|
||||
Lcd_SetPixel(i, j, g_tCVsr.wBackColor);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Lcd_Pub_UTF8(x, y, unicode);
|
||||
x += g_tCVsr.wGBFontWidth;
|
||||
}
|
||||
}
|
||||
/* ---------- 单字节字符(ASCII):n=1 ---------- */
|
||||
else
|
||||
{
|
||||
if (unicode == 0x0a)
|
||||
{
|
||||
/* 换行符:x 回到行首,y 下移一行高度 + 行距 */
|
||||
x = bakx;
|
||||
y += g_tCVsr.wGBFontHeight + g_tDspAttrib.rowSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 控制字符(0x00..0x0F 且非 0x0A)不绘制,仅跳过 */
|
||||
if (unicode >= 0x10)
|
||||
{
|
||||
/* 本行放不下一个 ASCII 时,向后查找换行符并换行 */
|
||||
if (x > LCD_SIZE_X - g_tCVsr.wASCIIFontWidth)
|
||||
{
|
||||
/*擦除本行剩余部分*/
|
||||
for(uint16_t j = y; j < y + g_tCVsr.wASCIIFontHeight; j++)
|
||||
{
|
||||
for(uint16_t i = x; i < LCD_SIZE_X; i++)
|
||||
{
|
||||
Lcd_SetPixel(i, j, g_tCVsr.wBackColor);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Lcd_Pub_Ascii(x, y, (uint8_t)unicode);
|
||||
x += g_tCVsr.wASCIIFontWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
index += n; /* 已处理完当前字符,下标移到下一字符 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#ifndef __LCD__H__
|
||||
#define __LCD__H__
|
||||
|
||||
#include "../../include/types.h"
|
||||
|
||||
|
||||
|
||||
/* 单色液晶屏幕 160*160
|
||||
一个 byte 代表一个像素点
|
||||
0xFF 代表白色
|
||||
0x00 代表黑色
|
||||
*/
|
||||
#define LCD_SIZE_X 160
|
||||
#define LCD_SIZE_Y 160
|
||||
|
||||
|
||||
#define LCD_LINE_SIZE LCD_SIZE_X
|
||||
|
||||
#define LCD_DISPLAYMEMORYSIZE (LCD_SIZE_X * LCD_SIZE_Y)
|
||||
|
||||
#define LCD_COLOR_WHITE 0xFF
|
||||
#define LCD_COLOR_BLACK 0x00
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t pwbyLCDMemory[LCD_DISPLAYMEMORYSIZE]; //定义显存
|
||||
uint8_t wFontColor; // 字体颜色
|
||||
uint8_t wBackColor; // 字符显示背景颜色
|
||||
uint8_t wGBFontWidth; // 汉字字体宽度
|
||||
uint8_t wGBFontHeight; // 汉字字体高度
|
||||
uint8_t wASCIIFontWidth; // 字符字体宽度
|
||||
uint8_t wASCIIFontHeight; // 字符字体高度
|
||||
uint16_t *pwLibHZ; // 汉字库地址
|
||||
uint8_t *pbyLibAscii; // ASCII库地址
|
||||
} tagScreenControl;
|
||||
|
||||
/* 当前界面/显存(lcd.c 中定义,供 remoteDisplay 等模块读取显存) */
|
||||
extern tagScreenControl g_tCVsr;
|
||||
|
||||
typedef struct { // 显示属性数据结构
|
||||
uint16_t style:1; // 显示方式 <0=正常显示; 1=返显>
|
||||
uint16_t StringDirect:1; // 字符显示方向<0=横向显示, 1=竖向显示>
|
||||
uint16_t fillZero:1; // 10进制数显示前面是否补0。0=不补0,1=补0;
|
||||
uint16_t Rotate:1; // 字符是否旋转显示,目前只支持逆时针转90度
|
||||
uint16_t res:6; // 保留
|
||||
uint16_t rowSpace:4; // 行距
|
||||
}tagDspAttrib, *tagPDspAttrib;
|
||||
|
||||
|
||||
void Lcd_Init(void);
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color);
|
||||
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y);
|
||||
void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color);
|
||||
void Lcd_MeiTouPic(uint16_t wYStart, uint16_t wWidth);
|
||||
void Lcd_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y);
|
||||
void Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend);
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color);
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color);
|
||||
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode);
|
||||
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef __ASCII_H__
|
||||
#define __ASCII_H__
|
||||
|
||||
#include "../../include/types.h"
|
||||
#include "types.h"
|
||||
|
||||
/* 8x8 ASCII 点阵表 */
|
||||
extern const uint8_t g_abyASCII88[][8];
|
||||
@@ -12,13 +12,10 @@ extern uint8_t g_abyASCII126[][12];
|
||||
/* 16x8 ASCII 点阵表 */
|
||||
extern uint8_t g_abyASCII168[][16];
|
||||
|
||||
|
||||
|
||||
#define UTF8_HZ12_BYTES_PER_CHAR 24
|
||||
#define UTF8_HZ12_NUM_CHARS 7038
|
||||
|
||||
/* 按 Unicode 码点查找点阵,返回 24 字节指针,未找到返回 NULL */
|
||||
const uint8_t* utf8_hz12_get(uint32_t unicode);
|
||||
|
||||
|
||||
#endif /* ASCII_FONT_TABLES_H */
|
||||
133
src/Drv/lcd/lcd.c
Normal file
133
src/Drv/lcd/lcd.c
Normal file
@@ -0,0 +1,133 @@
|
||||
/* -------------------------------------------------------------------------
|
||||
* 文件名: lcd.c
|
||||
* 模块职责:
|
||||
* 提供 LCD 显存(framebuffer)的最小抽象接口:
|
||||
* 1) 初始化显存
|
||||
* 2) 单像素写入
|
||||
* 3) 单像素读取
|
||||
* 4) 获取显存首地址(供上层批量处理)
|
||||
*
|
||||
* 设计说明:
|
||||
* - 当前实现是“内存模拟 LCD”:用一段线性数组表示屏幕像素。
|
||||
* - 坐标系采用左上角原点:
|
||||
* x: [0, LCD_SIZE_X-1],向右递增
|
||||
* y: [0, LCD_SIZE_Y-1],向下递增
|
||||
* - 像素索引映射关系:
|
||||
* index = y * LCD_LINE_SIZE + x
|
||||
* - 越界策略:
|
||||
* 写操作:直接忽略(安全返回)
|
||||
* 读操作:返回 0(黑色/默认值)
|
||||
* ------------------------------------------------------------------------- */
|
||||
#include "lcd.h"
|
||||
#include <string.h>
|
||||
|
||||
/* LCD 显存数组(模块私有):
|
||||
* - static 限定作用域在本翻译单元内,避免外部直接访问破坏封装
|
||||
* - 每个元素对应一个像素点,大小为 LCD_DISPLAYMEMORYSIZE
|
||||
*/
|
||||
static uint8_t lcd_memory[LCD_DISPLAYMEMORYSIZE];
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_Init
|
||||
* 功能:
|
||||
* 初始化 LCD 显存,将所有像素清为 0(黑色背景)。
|
||||
*
|
||||
* 实现细节:
|
||||
* - 使用 memset 一次性清零整个显存,时间复杂度 O(N)。
|
||||
* - sizeof(lcd_memory) 保证长度与数组实际大小一致,避免硬编码。
|
||||
*
|
||||
* 输入参数:
|
||||
* 无
|
||||
* 返回值:
|
||||
* 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void Lcd_Init(void)
|
||||
{
|
||||
memset(lcd_memory, 0, sizeof(lcd_memory));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_SetPixel
|
||||
* 功能:
|
||||
* 设置指定坐标 (x, y) 的像素值。
|
||||
*
|
||||
* 参数:
|
||||
* x - 像素横坐标
|
||||
* y - 像素纵坐标
|
||||
* color - 要写入的颜色值(单字节)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 x 或 y 越界,函数返回 LCD_ERR_OUT_OF_RANGE,不做任何写入。
|
||||
* - 这样可防止数组越界访问导致内存破坏,并允许上层检测错误。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数对颜色值做白名单校验,仅允许 LCD_FONT / LCD_BACK。
|
||||
* 返回值:
|
||||
* - LCD_OK (0): 写入成功
|
||||
* - LCD_ERR_OUT_OF_RANGE (-1): 坐标越界
|
||||
* - LCD_ERR_INVALID_COLOR (-2): 非法颜色值
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color)
|
||||
{
|
||||
/* 坐标越界保护:超出屏幕范围返回错误码 */
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
{
|
||||
return LCD_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
/* 颜色值白名单:仅允许前景色与背景色 */
|
||||
if (color != LCD_FONT && color != LCD_BACK)
|
||||
{
|
||||
return LCD_ERR_INVALID_COLOR;
|
||||
}
|
||||
|
||||
/* 二维坐标转线性索引并写入像素 */
|
||||
lcd_memory[y * LCD_LINE_SIZE + x] = color;
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_GetPixel
|
||||
* 功能:
|
||||
* 读取指定坐标 (x, y) 的像素值。
|
||||
*
|
||||
* 参数:
|
||||
* x - 像素横坐标
|
||||
* y - 像素纵坐标
|
||||
*
|
||||
* 返回值:
|
||||
* - 0~255: 合法坐标对应的像素值
|
||||
* - LCD_ERR_OUT_OF_RANGE (-1): 坐标越界
|
||||
*
|
||||
* 设计考虑:
|
||||
* - 使用错误码让调用方可显式区分“黑色像素(0)”与“越界读取(-1)”。
|
||||
* - 与 Lcd_SetPixel 的错误码风格保持一致,便于统一测试和上层处理。
|
||||
* ------------------------------------------------------------------------- */
|
||||
int16_t Lcd_GetPixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
/* 越界读取保护:返回错误码 */
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
{
|
||||
return LCD_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
/* 二维坐标转线性索引并返回像素值 */
|
||||
return lcd_memory[y * LCD_LINE_SIZE + x];
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_GetFrameBuffer
|
||||
* 功能:
|
||||
* 返回 LCD 显存首地址,供上层进行批量处理(如反显、整屏传输等)。
|
||||
*
|
||||
* 返回值:
|
||||
* uint8_t* - 指向 lcd_memory[0] 的指针
|
||||
*
|
||||
* 使用约束:
|
||||
* - 调用方应保证访问范围不超过 LCD_DISPLAYMEMORYSIZE。
|
||||
* - 该接口暴露底层内存,适合高性能场景,但需调用方自律维护边界。
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t* Lcd_GetFrameBuffer(void)
|
||||
{
|
||||
return lcd_memory;
|
||||
}
|
||||
31
src/Drv/lcd/lcd.h
Normal file
31
src/Drv/lcd/lcd.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef __LCD__H__
|
||||
#define __LCD__H__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* 单色液晶屏幕 160*160
|
||||
一个 byte 代表一个像素点
|
||||
0xFF 代表白色
|
||||
0x00 代表黑色
|
||||
*/
|
||||
#define LCD_SIZE_X 160
|
||||
#define LCD_SIZE_Y 160
|
||||
|
||||
#define LCD_LINE_SIZE LCD_SIZE_X
|
||||
#define LCD_DISPLAYMEMORYSIZE (LCD_SIZE_X * LCD_SIZE_Y)
|
||||
|
||||
#define LCD_COLOR_WHITE 0xFF
|
||||
#define LCD_COLOR_BLACK 0x00
|
||||
|
||||
#define LCD_FONT LCD_COLOR_WHITE
|
||||
#define LCD_BACK LCD_COLOR_BLACK
|
||||
#define LCD_OK 0
|
||||
#define LCD_ERR_OUT_OF_RANGE -1
|
||||
#define LCD_ERR_INVALID_COLOR -2
|
||||
|
||||
void Lcd_Init(void);
|
||||
int8_t Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color);
|
||||
int16_t Lcd_GetPixel(uint16_t x, uint16_t y);
|
||||
uint8_t* Lcd_GetFrameBuffer(void);
|
||||
|
||||
#endif
|
||||
225
src/Drv/lcd/lcd_draw.c
Normal file
225
src/Drv/lcd/lcd_draw.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/* -------------------------------------------------------------------------
|
||||
* 文件名: lcd_draw.c
|
||||
* 模块职责:
|
||||
* 提供基于像素接口的基础绘图能力,包含:
|
||||
* 1) 矩形填充(Lcd_FillRect)
|
||||
* 2) 水平线绘制(Lcd_LineH)
|
||||
* 3) 垂直线绘制(Lcd_LineV)
|
||||
* 4) 区域反显(Lcd_Invert)
|
||||
*
|
||||
* 设计说明:
|
||||
* - 本模块是 lcd.c 的上层,复用 Lcd_SetPixel/Lcd_GetFrameBuffer。
|
||||
* - 当前模块在入口做参数合法性检查,发生越界/非法颜色时直接返回错误码。
|
||||
* - 坐标区间语义在不同函数中不同,使用时需特别注意(见各函数注释)。
|
||||
* ------------------------------------------------------------------------- */
|
||||
#include "lcd_draw.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
static int8_t Lcd_ColorCheck(uint32_t color)
|
||||
{
|
||||
if ((color != LCD_FONT) && (color != LCD_BACK)) {
|
||||
return LCD_ERR_INVALID_COLOR;
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_FillRect
|
||||
* 功能:
|
||||
* 用指定颜色填充一个矩形区域。
|
||||
*
|
||||
* 参数:
|
||||
* left_x - 左上角 x(包含)
|
||||
* top_y - 左上角 y(包含)
|
||||
* right_x - 右下角 x(包含)
|
||||
* bottom_y - 右下角 y(包含)
|
||||
* color - 填充颜色
|
||||
*
|
||||
* 区间语义:
|
||||
* - 采用“闭区间”遍历:
|
||||
* x: [left_x, right_x]
|
||||
* y: [top_y, bottom_y]
|
||||
*
|
||||
* 注意:
|
||||
* - 若 right_x < left_x 或 bottom_y < top_y,当前实现不会进入循环(或可能因无符号导致异常遍历风险),
|
||||
* 调用方应保证坐标顺序合法。
|
||||
* 返回值:
|
||||
* - LCD_OK: 参数合法且绘制完成
|
||||
* - LCD_ERR_OUT_OF_RANGE: 坐标越界或顺序非法
|
||||
* - LCD_ERR_INVALID_COLOR: 颜色非法
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color)
|
||||
{
|
||||
if (Lcd_ColorCheck(color) != LCD_OK) return LCD_ERR_INVALID_COLOR;
|
||||
if ((right_x < left_x) || (bottom_y < top_y)) return LCD_ERR_OUT_OF_RANGE;
|
||||
if ((right_x >= LCD_SIZE_X) || (bottom_y >= LCD_SIZE_Y)) return LCD_ERR_OUT_OF_RANGE;
|
||||
|
||||
for (uint16_t y = top_y; y <= bottom_y; y++) {
|
||||
for (uint16_t x = left_x; x <= right_x; x++) {
|
||||
Lcd_SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* 宏: M_GuiSwap
|
||||
* 作用:
|
||||
* 使用异或交换法交换两个同类型变量的值。
|
||||
* 使用场景:
|
||||
* 在起止坐标反向时进行修正(如 xStart > xEnd)。
|
||||
* 注意:
|
||||
* 该写法要求 a 与 b 指向不同变量;若传入同一变量会把值清零。
|
||||
*/
|
||||
#define M_GuiSwap(a, b) { a ^= b; b ^= a; a ^= b; }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_LineH
|
||||
* 功能:
|
||||
* 绘制水平线(可设置线宽)。
|
||||
*
|
||||
* 参数:
|
||||
* wXStart - 起始 x
|
||||
* wXEnd - 结束 x
|
||||
* wYStart - 起始 y
|
||||
* wWidth - 线宽(沿 y 方向扩展)
|
||||
* color - 线条颜色
|
||||
*
|
||||
* 区间语义:
|
||||
* - x 方向采用半开区间 [wXStart, wXEnd)
|
||||
* - y 方向采用半开区间 [wYStart, wYStart + wWidth)
|
||||
*
|
||||
* 说明:
|
||||
* - 当 wXEnd < wXStart 时会自动交换两者,支持反向输入。
|
||||
* 返回值:
|
||||
* - LCD_OK: 参数合法且绘制完成
|
||||
* - LCD_ERR_OUT_OF_RANGE: 坐标越界
|
||||
* - LCD_ERR_INVALID_COLOR: 颜色非法
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint32_t wYEnd32;
|
||||
uint16_t wYEnd;
|
||||
|
||||
if (Lcd_ColorCheck(color) != LCD_OK) return LCD_ERR_INVALID_COLOR;
|
||||
if (wXEnd < wXStart) {
|
||||
M_GuiSwap(wXEnd, wXStart);
|
||||
}
|
||||
wYEnd32 = (uint32_t)wYStart + (uint32_t)wWidth;
|
||||
if (wXStart >= LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE;
|
||||
if (wXEnd > LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE; /* 半开区间,end 可等于边界 */
|
||||
if (wYStart >= LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE;
|
||||
if (wYEnd32 > LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE; /* 半开区间,end 可等于边界 */
|
||||
wYEnd = (uint16_t)wYEnd32;
|
||||
|
||||
for (uint16_t x = wXStart; x < wXEnd; x++) {
|
||||
for (uint16_t y = wYStart; y < wYEnd; y++) {
|
||||
Lcd_SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_LineV
|
||||
* 功能:
|
||||
* 绘制垂直线(可设置线宽)。
|
||||
*
|
||||
* 参数:
|
||||
* wYStart - 起始 y
|
||||
* wYEnd - 结束 y
|
||||
* wXStart - 起始 x
|
||||
* wWidth - 线宽(沿 x 方向扩展)
|
||||
* color - 线条颜色
|
||||
*
|
||||
* 区间语义:
|
||||
* - y 方向采用半开区间 [wYStart, wYEnd)
|
||||
* - x 方向采用半开区间 [wXStart, wXStart + wWidth)
|
||||
*
|
||||
* 说明:
|
||||
* - 当 wYEnd < wYStart 时会自动交换两者,支持反向输入。
|
||||
*
|
||||
* 返回值:
|
||||
* - LCD_OK: 参数合法且绘制完成
|
||||
* - LCD_ERR_OUT_OF_RANGE: 坐标越界
|
||||
* - LCD_ERR_INVALID_COLOR: 颜色非法
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint32_t wXEnd32;
|
||||
uint16_t wXEnd;
|
||||
|
||||
if (Lcd_ColorCheck(color) != LCD_OK) return LCD_ERR_INVALID_COLOR;
|
||||
if (wYEnd < wYStart) {
|
||||
M_GuiSwap(wYEnd, wYStart);
|
||||
}
|
||||
wXEnd32 = (uint32_t)wXStart + (uint32_t)wWidth;
|
||||
if (wYStart >= LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE;
|
||||
if (wYEnd > LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE; /* 半开区间,end 可等于边界 */
|
||||
if (wXStart >= LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE;
|
||||
if (wXEnd32 > LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE; /* 半开区间,end 可等于边界 */
|
||||
wXEnd = (uint16_t)wXEnd32;
|
||||
|
||||
for (uint16_t x = wXStart; x < wXEnd; x++) {
|
||||
for (uint16_t y = wYStart; y < wYEnd; y++) {
|
||||
Lcd_SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* 宏: M_Max / M_Min
|
||||
* 作用:
|
||||
* 计算两个值的较大/较小值,用于将任意方向输入规范化为最小/最大边界。
|
||||
*/
|
||||
#define M_Max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#define M_Min(a, b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_Invert
|
||||
* 功能:
|
||||
* 对指定矩形区域像素执行按位取反,实现反显效果。
|
||||
*
|
||||
* 参数:
|
||||
* wXstart, wYstart - 第一个角点
|
||||
* wXend, wYend - 第二个角点
|
||||
*
|
||||
* 区间语义:
|
||||
* - 先通过 min/max 规范化坐标后,按半开区间处理:
|
||||
* x: [xMin, xMax)
|
||||
* y: [yMin, yMax)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若任一端点超出屏幕范围(> LCD_SIZE_X / > LCD_SIZE_Y),函数返回错误码。
|
||||
* - 这里通过直接访问 framebuffer 提升效率,不逐点调用 Lcd_SetPixel。
|
||||
*
|
||||
* 注意:
|
||||
* - 本函数使用线性地址 `y * LCD_SIZE_X + x` 访问显存,默认与 lcd.c 的布局一致。
|
||||
* 返回值:
|
||||
* - LCD_OK: 参数合法且处理完成
|
||||
* - LCD_ERR_OUT_OF_RANGE: 坐标越界
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend)
|
||||
{
|
||||
uint16_t xMin, xMax, yMin, yMax;
|
||||
uint8_t wColor;
|
||||
uint8_t *framebuffer = Lcd_GetFrameBuffer();
|
||||
|
||||
if ((wXstart > LCD_SIZE_X) || (wXend > LCD_SIZE_X)) return LCD_ERR_OUT_OF_RANGE;
|
||||
if ((wYstart > LCD_SIZE_Y) || (wYend > LCD_SIZE_Y)) return LCD_ERR_OUT_OF_RANGE;
|
||||
|
||||
xMin = M_Min(wXstart, wXend);
|
||||
yMin = M_Min(wYstart, wYend);
|
||||
xMax = M_Max(wXstart, wXend);
|
||||
yMax = M_Max(wYstart, wYend);
|
||||
|
||||
for (uint16_t y = yMin; y < yMax; y++) {
|
||||
for (uint16_t x = xMin; x < xMax; x++) {
|
||||
wColor = framebuffer[y * LCD_SIZE_X + x];
|
||||
framebuffer[y * LCD_SIZE_X + x] = (uint8_t)~wColor;
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
13
src/Drv/lcd/lcd_draw.h
Normal file
13
src/Drv/lcd/lcd_draw.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef __LCD_DRAW_H__
|
||||
#define __LCD_DRAW_H__
|
||||
|
||||
#include "lcd.h"
|
||||
|
||||
|
||||
|
||||
int8_t Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color);
|
||||
int8_t Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend);
|
||||
int8_t Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color);
|
||||
int8_t Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color);
|
||||
|
||||
#endif
|
||||
244
src/Drv/lcd/lcd_text.c
Normal file
244
src/Drv/lcd/lcd_text.c
Normal file
@@ -0,0 +1,244 @@
|
||||
/* -------------------------------------------------------------------------
|
||||
* 文件名: lcd_text.c
|
||||
* 模块职责:
|
||||
* 提供文本渲染与 UTF-8 解析能力,包含:
|
||||
* 1) UTF-8 单字符解码(utf8_next)
|
||||
* 2) ASCII 字符绘制(内部函数 Lcd_Pub_Ascii)
|
||||
* 3) 中文位图绘制(内部函数 Lcd_Pub_UTF8)
|
||||
* 4) 字符串渲染入口(Lcd_ShowStr)
|
||||
*
|
||||
* 设计说明:
|
||||
* - 显示像素最终通过 Lcd_SetPixel 写入显存。
|
||||
* - 当前字体配置固定为:
|
||||
* ASCII: 7x12
|
||||
* 中文: 13x12(来自 utf8_hz12_get)
|
||||
* - 对越界场景采用“显式错误返回”,便于上层与测试捕获。
|
||||
* ------------------------------------------------------------------------- */
|
||||
#include "lcd_text.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ascii.h"
|
||||
#include "lcd_draw.h"
|
||||
|
||||
static textConfig text_cfg = {
|
||||
.wGBFontWidth = 13,
|
||||
.wGBFontHeight = 12,
|
||||
.wASCIIFontWidth = 7,
|
||||
.wASCIIFontHeight = 12,
|
||||
.pbyLibAscii = g_abyASCII126[0],
|
||||
.rowSpace = 2
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: utf8_next
|
||||
* 功能:
|
||||
* 从 UTF-8 字节流当前位置解析“一个字符”,输出 Unicode 码点与字节长度。
|
||||
*
|
||||
* 参数:
|
||||
* utf8 - 指向当前待解析字节
|
||||
* out_unicode - 输出解析得到的 Unicode 码点
|
||||
*
|
||||
* 返回值:
|
||||
* 0 : 解析失败/字符串结束
|
||||
* 1 : ASCII 单字节
|
||||
* 2 : 两字节 UTF-8
|
||||
* 3 : 三字节 UTF-8
|
||||
*
|
||||
* 注意:
|
||||
* - 当前实现不支持 4 字节 UTF-8(如 emoji)。
|
||||
* - 对截断序列(缺少后续字节)返回 0。
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
|
||||
{
|
||||
unsigned char c = utf8[0];
|
||||
if (c == 0) {
|
||||
*out_unicode = 0;
|
||||
return 0;
|
||||
}
|
||||
if (c < 0x80) {
|
||||
*out_unicode = c;
|
||||
return 1;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
if (utf8[1] == 0) return 0;
|
||||
*out_unicode = (uint32_t)((c & 0x1F) << 6 | (utf8[1] & 0x3F));
|
||||
return 2;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0) {
|
||||
if (utf8[1] == 0 || utf8[2] == 0) return 0;
|
||||
*out_unicode = (uint32_t)((c & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F));
|
||||
return 3;
|
||||
}
|
||||
*out_unicode = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_Pub_Ascii(内部)
|
||||
* 功能:
|
||||
* 按 ASCII 点阵库将单个 ASCII 字符绘制到指定坐标。
|
||||
*
|
||||
* 参数:
|
||||
* x, y - 字符左上角坐标
|
||||
* byAscii - ASCII 码值
|
||||
*
|
||||
* 返回值:
|
||||
* LCD_OK / LCD_ERR_OUT_OF_RANGE
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int8_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||
{
|
||||
const textConfig *cfg = &text_cfg;
|
||||
uint8_t i, j;
|
||||
uint8_t byLine, *pbyFontLib;
|
||||
|
||||
/* 按字库行高定位字符位图起始地址 */
|
||||
pbyFontLib = &cfg->pbyLibAscii[byAscii * cfg->wASCIIFontHeight];
|
||||
if ((x + cfg->wASCIIFontWidth) > LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE;
|
||||
if ((y + cfg->wASCIIFontHeight) > LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE;
|
||||
|
||||
|
||||
for (j = 0; j < cfg->wASCIIFontHeight; j++) {
|
||||
byLine = pbyFontLib[j];
|
||||
for (i = 0; i < cfg->wASCIIFontWidth; i++) {
|
||||
uint8_t bit_on = ((byLine << i) & 0x80) != 0;
|
||||
uint16_t px, py;
|
||||
uint8_t color = bit_on ? LCD_FONT : LCD_BACK;
|
||||
px = x + i;
|
||||
py = y + j;
|
||||
if (Lcd_SetPixel(px, py, color) != LCD_OK) {
|
||||
return LCD_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_Pub_UTF8(内部)
|
||||
* 功能:
|
||||
* 将一个 Unicode 字符(中文)按 13x12 位图绘制到指定坐标。
|
||||
*
|
||||
* 参数:
|
||||
* x, y - 字符左上角坐标
|
||||
* unicode - Unicode 码点
|
||||
*
|
||||
* 返回值:
|
||||
* LCD_OK / LCD_ERR_OUT_OF_RANGE
|
||||
*
|
||||
* 注意:
|
||||
* - 若字库中找不到该字符位图,当前实现按“空操作成功”处理(返回 LCD_OK)。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int8_t Lcd_Pub_UTF8(uint16_t x, uint16_t y, uint32_t unicode)
|
||||
{
|
||||
const textConfig *cfg = &text_cfg;
|
||||
const uint8_t *bitmap = utf8_hz12_get(unicode);
|
||||
uint16_t word = 0;
|
||||
if ((x + cfg->wGBFontWidth) > LCD_SIZE_X) return LCD_ERR_OUT_OF_RANGE;
|
||||
if ((y + cfg->wGBFontHeight) > LCD_SIZE_Y) return LCD_ERR_OUT_OF_RANGE;
|
||||
if (bitmap == NULL) return LCD_OK;
|
||||
for (uint8_t j = 0; j < cfg->wGBFontHeight; j++) {
|
||||
word = (uint16_t)((bitmap[j * 2] << 8) | bitmap[j * 2 + 1]);
|
||||
for (uint8_t i = 0; i < cfg->wGBFontWidth; i++) {
|
||||
if ((word >> (15 - i)) & 1) {
|
||||
if (Lcd_SetPixel(x + i, y + j, LCD_FONT) != LCD_OK) {
|
||||
return LCD_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
if (Lcd_SetPixel(x + i, y + j, LCD_BACK) != LCD_OK) {
|
||||
return LCD_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return LCD_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_ShowStr
|
||||
* 功能:
|
||||
* 在指定坐标绘制 UTF-8 字符串,支持 ASCII、中文与换行。
|
||||
*
|
||||
* 参数:
|
||||
* x, y - 首字符左上角坐标
|
||||
* pcString - UTF-8 字符串(以 '\0' 结尾)
|
||||
*
|
||||
* 返回值:
|
||||
* 0 : 绘制成功
|
||||
* -1 : x 方向越界
|
||||
* -2 : y 方向越界
|
||||
*
|
||||
* 说明:
|
||||
* - 与历史行为兼容,保留 -1/-2 两种错误码语义。
|
||||
* - 内部会在渲染前做边界预判,并在底层写像素失败时转化为越界错误返回。
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
{
|
||||
const textConfig *cfg = &text_cfg;
|
||||
uint16_t bakx = x;
|
||||
uint32_t unicode;
|
||||
uint16_t index = 0;
|
||||
uint8_t n = 0;
|
||||
|
||||
if (x > LCD_SIZE_X - cfg->wASCIIFontWidth) return -1;
|
||||
if (y >= LCD_SIZE_Y - cfg->wGBFontHeight) return -2;
|
||||
while (pcString[index] != 0x0) {
|
||||
n = utf8_next(pcString + index, &unicode);
|
||||
if (n <= 0) break;
|
||||
if (n > 1) {
|
||||
/* 中文字符渲染路径(13x12) */
|
||||
if (x > LCD_SIZE_X - cfg->wGBFontWidth) {
|
||||
for (uint16_t j = y; j < y + cfg->wGBFontHeight; j++) {
|
||||
for (uint16_t i = x; i < LCD_SIZE_X; i++) {
|
||||
Lcd_SetPixel(i, j, LCD_BACK);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
if (Lcd_Pub_UTF8(x, y, unicode) != LCD_OK) {
|
||||
return -2;
|
||||
}
|
||||
x += cfg->wGBFontWidth;
|
||||
}
|
||||
} else {
|
||||
/* ASCII / 控制字符渲染路径 */
|
||||
if (unicode == 0x0a) {
|
||||
/* 换行:x 回到行首,y 下移一行(字高 + 行距) */
|
||||
x = bakx;
|
||||
y += cfg->wGBFontHeight + cfg->rowSpace;
|
||||
if (y >= LCD_SIZE_Y - cfg->wGBFontHeight) {
|
||||
return -2;
|
||||
}
|
||||
} else if (unicode >= 0x10) {
|
||||
if (x > LCD_SIZE_X - cfg->wASCIIFontWidth) {
|
||||
for (uint16_t j = y; j < y + cfg->wASCIIFontHeight; j++) {
|
||||
for (uint16_t i = x; i < LCD_SIZE_X; i++) {
|
||||
Lcd_SetPixel(i, j, LCD_BACK);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
if (Lcd_Pub_Ascii(x, y, (uint8_t)unicode) != LCD_OK) {
|
||||
return -1;
|
||||
}
|
||||
x += cfg->wASCIIFontWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: Lcd_ShowTest
|
||||
* 功能:
|
||||
* 预留测试接口(当前为桩实现)。
|
||||
* ------------------------------------------------------------------------- */
|
||||
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)pcString;
|
||||
return 0;
|
||||
}
|
||||
19
src/Drv/lcd/lcd_text.h
Normal file
19
src/Drv/lcd/lcd_text.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef __LCD_TEXT_H__
|
||||
#define __LCD_TEXT_H__
|
||||
|
||||
#include "lcd.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t wGBFontWidth;
|
||||
uint8_t wGBFontHeight;
|
||||
uint8_t wASCIIFontWidth;
|
||||
uint8_t wASCIIFontHeight;
|
||||
uint8_t *pbyLibAscii;
|
||||
uint16_t rowSpace;
|
||||
} textConfig;
|
||||
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode);
|
||||
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
|
||||
#endif
|
||||
125
src/Drv/menu.c
125
src/Drv/menu.c
@@ -1,8 +1,11 @@
|
||||
#include "menu.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "lcd.h"
|
||||
#include "lcd/lcd.h"
|
||||
#include "lcd/lcd_draw.h"
|
||||
#include "lcd/lcd_text.h"
|
||||
#include "display.h"
|
||||
#include "key.h"
|
||||
|
||||
@@ -99,6 +102,98 @@ tagMenuCtrl g_tMenuCtrl; /* 菜单全局
|
||||
|
||||
tagMenuItem g_tMenuItem[300]; // 菜单链表空间定义
|
||||
|
||||
#define MENU_GUI_SWAP(a, b) \
|
||||
do { \
|
||||
(a) ^= (b); \
|
||||
(b) ^= (a); \
|
||||
(a) ^= (b); \
|
||||
} while (0)
|
||||
|
||||
static void Menu_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wDX, wDY, wSignY, wSignX, wDecision;
|
||||
uint16_t wCurx, wCury, wNextx, wNexty, wPY, wPX;
|
||||
|
||||
if (wYstart == wYend) {
|
||||
Lcd_LineH(wXstart, wXend, wYstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
if (wXstart == wXend) {
|
||||
Lcd_LineV(wYstart, wYend, wXstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
|
||||
wDX = (uint16_t)abs((int)wXstart - (int)wXend);
|
||||
wDY = (uint16_t)abs((int)wYstart - (int)wYend);
|
||||
if (((wDX >= wDY && (wXstart > wXend)) || ((wDY > wDX) && (wYstart > wYend)))) {
|
||||
MENU_GUI_SWAP(wXend, wXstart);
|
||||
MENU_GUI_SWAP(wYend, wYstart);
|
||||
}
|
||||
wSignY = (wYend - wYstart) / wDY;
|
||||
wSignX = (wXend - wXstart) / wDX;
|
||||
|
||||
if (wDX >= wDY) {
|
||||
wCurx = wXstart;
|
||||
wCury = wYstart;
|
||||
wNextx = wXend;
|
||||
wNexty = wYend;
|
||||
wDecision = (wDX >> 1);
|
||||
for (; wCurx <= wNextx;) {
|
||||
if (wDecision >= wDX) {
|
||||
wDecision -= wDX;
|
||||
wCury += wSignY;
|
||||
wNexty -= wSignY;
|
||||
}
|
||||
for (wPY = wCury - wWidth / 2; wPY <= wCury + wWidth / 2; wPY++) {
|
||||
Lcd_SetPixel(wCurx, wPY, color);
|
||||
}
|
||||
for (wPY = wNexty - wWidth / 2; wPY <= wNexty + wWidth / 2; wPY++) {
|
||||
Lcd_SetPixel(wNextx, wPY, color);
|
||||
}
|
||||
wCurx++;
|
||||
wNextx--;
|
||||
wDecision += wDY;
|
||||
}
|
||||
} else {
|
||||
wCurx = wXstart;
|
||||
wCury = wYstart;
|
||||
wNextx = wXend;
|
||||
wNexty = wYend;
|
||||
wDecision = (wDY >> 1);
|
||||
for (; wCury <= wNexty;) {
|
||||
if (wDecision >= wDY) {
|
||||
wDecision -= wDY;
|
||||
wCurx += wSignX;
|
||||
wNextx -= wSignX;
|
||||
}
|
||||
for (wPX = wCurx - wWidth / 2; wPX <= wCurx + wWidth / 2; wPX++) {
|
||||
Lcd_SetPixel(wPX, wCury, color);
|
||||
}
|
||||
for (wPX = wNextx - wWidth / 2; wPX <= wNextx + wWidth / 2; wPX++) {
|
||||
Lcd_SetPixel(wPX, wNexty, color);
|
||||
}
|
||||
wCury++;
|
||||
wNexty--;
|
||||
wDecision += wDX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Menu_MeiTouPic(uint16_t wYStart, uint16_t wWidth)
|
||||
{
|
||||
Lcd_LineH(16, 144, wYStart, wWidth, LCD_FONT);
|
||||
Menu_Line(8, wYStart - 8, 16, wYStart, wWidth, LCD_FONT);
|
||||
Menu_Line(144, wYStart, 152, wYStart - 8, wWidth, LCD_FONT);
|
||||
}
|
||||
|
||||
static void Menu_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y)
|
||||
{
|
||||
Lcd_LineH(left_x, right_x, top_y, 1, LCD_FONT);
|
||||
Lcd_LineV(top_y, bottom_y, left_x, 1, LCD_FONT);
|
||||
Lcd_LineH(left_x, right_x, bottom_y, 1, LCD_FONT);
|
||||
Lcd_LineV(top_y, bottom_y, right_x, 1, LCD_FONT);
|
||||
}
|
||||
|
||||
void Menu_0LevelNumCal(void)
|
||||
{
|
||||
tagPMenuModel ptModelIndex; /* 当前遍历到的菜单表项指针 */
|
||||
@@ -614,17 +709,17 @@ void Menu_PositionCal(tagPMenuItem ptMenuHead)
|
||||
{
|
||||
for (uint16_t x = left_x; x < right_x; x++)
|
||||
{
|
||||
Lcd_SetPixel(x, y, g_tCVsr.wBackColor); /* 设置按钮内部像素为前景色,实现实心效果 */
|
||||
Lcd_SetPixel(x, y, LCD_BACK); /* 设置按钮内部像素为前景色,实现实心效果 */
|
||||
}
|
||||
}
|
||||
/* 绘制上边框:从 (left_x, top_y) 到 (right_x, top_y),线宽 1 像素 */
|
||||
Lcd_LineH(left_x, right_x, top_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineH(left_x, right_x, top_y, 1, LCD_FONT);
|
||||
/* 绘制左边框:从 (left_x, top_y) 到 (left_x, bottom_y),线宽 1 像素 */
|
||||
Lcd_LineV(top_y, bottom_y, left_x, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y, left_x, 1, LCD_FONT);
|
||||
/* 绘制下边框:从 (left_x, bottom_y) 到 (right_x+1, bottom_y),稍微向右多画 1 像素防止边界漏点 */
|
||||
Lcd_LineH(left_x, right_x + 1, bottom_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineH(left_x, right_x + 1, bottom_y, 1, LCD_FONT);
|
||||
/* 绘制右边框:从 (right_x, top_y) 到 (right_x, bottom_y),线宽 1 像素 */
|
||||
Lcd_LineV(top_y, bottom_y, right_x, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y, right_x, 1, LCD_FONT);
|
||||
}
|
||||
/*
|
||||
这是分页的逻辑,需要保存,不能删除
|
||||
@@ -656,7 +751,7 @@ else if (byMenuNum > 20)
|
||||
byFirstPos = 1;
|
||||
wPosY = wEPosY - LINE_HEIGHT;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Menu_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↓"); // 第一页尾显示↓
|
||||
}
|
||||
else if ((byMenuPos < (byMaxNum * 2 - 2)) && (byPage > 2)) // 当前位置在第二页
|
||||
@@ -667,12 +762,12 @@ else if (byMenuNum > 20)
|
||||
byFirstPos = byMaxNum;
|
||||
wPosY = wSPosY + 2;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Menu_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↑"); // 第二页头显示↑
|
||||
|
||||
wPosY = wEPosY - LINE_HEIGHT;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Menu_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↓"); // 第一页尾显示↓
|
||||
}
|
||||
else
|
||||
@@ -690,7 +785,7 @@ else if (byMenuNum > 20)
|
||||
}
|
||||
wPosY = wSPosY + 2;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Menu_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↑"); // 第二页头显示↑
|
||||
}
|
||||
|
||||
@@ -809,15 +904,15 @@ void Menu_Show_Other(uint8_t bylevel)
|
||||
* 说明: 1. 所有 0 级菜单在 X 方向上按等间距分布
|
||||
* 2. 每次循环仅在菜单项仍在屏幕可见区域时才绘制对应标题文本
|
||||
* 3. 顶部 0~32 像素区域作为 0 级菜单标题栏背景,会被统一清屏并重绘
|
||||
* 4. 调用 Lcd_MeiTouPic 绘制“眉头”装饰线条,增强标题栏的视觉效果
|
||||
* 4. 调用 Menu_MeiTouPic 绘制“眉头”装饰线条,增强标题栏的视觉效果
|
||||
* 5. 示例中固定显示 “当前模式: 无模式”,后续可替换为实际运行模式文本
|
||||
*****************************************************************************/
|
||||
void Menu_Show_0Level()
|
||||
{
|
||||
/* 先清除顶部 0~32 像素高度区域,作为 0 级菜单标题栏背景 */
|
||||
Lcd_FillRect(0, 0, LCD_SIZE_X, 32, g_tCVsr.wBackColor);
|
||||
Lcd_FillRect(0, 0, LCD_SIZE_X, 32, LCD_BACK);
|
||||
/* 绘制“眉头”装饰,使 0 级菜单栏更加立体 */
|
||||
Lcd_MeiTouPic(16, 2 );
|
||||
Menu_MeiTouPic(16, 2 );
|
||||
Lcd_ShowStr(16, 20, (uint8_t*)"当前模式: 无模式" );
|
||||
}
|
||||
/******************************************************************************
|
||||
@@ -853,7 +948,7 @@ void Menu_Show_Proc(void)
|
||||
if (g_tMenuCtrl.pt0Level != g_tMenuCtrl.ptRoute[0])
|
||||
{
|
||||
bNeedFresh = 1; /* 需要整体刷新 */
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 清除 1~3 级菜单区域 */
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, LCD_BACK); /* 清除 1~3 级菜单区域 */
|
||||
g_tMenuCtrl.pt0Level = g_tMenuCtrl.ptRoute[0]; /* 更新 0 级路径起点 */
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 更新备份指针 */
|
||||
}
|
||||
@@ -872,7 +967,7 @@ void Menu_Show_Proc(void)
|
||||
#ifdef DEBUG
|
||||
printf("退层:从 %d 级退回 %d 级\n", g_tMenuCtrl.ptCurBak->byClass, g_tMenuCtrl.ptCurrent->byClass);
|
||||
#endif
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 擦除 1~3 级区域(保留 0~32 像素标题栏) */
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, LCD_BACK); /* 擦除 1~3 级区域(保留 0~32 像素标题栏) */
|
||||
bNeedFresh = 1;
|
||||
}
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 无论何种情况,更新备份指针为最新当前结点 */
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lcd.h" /* MENU_YMAX 依赖 LCD_SIZE_Y */
|
||||
#include "lcd/lcd.h" /* MENU_YMAX 依赖 LCD_SIZE_Y */
|
||||
|
||||
#define CN_HEIGHT 12 /* 菜单汉字高 */
|
||||
#define CN_ROWSPACE 2 // 菜单字符行间隔
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* ============================================================================
|
||||
/* ============================================================================
|
||||
* main.c - PC 端 HMI 菜单主程序
|
||||
* 功能:菜单交互(主线程)+ TCP 服务器(独立线程),按 Q 退出
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* remoteDisplay.c - TCP 服务器线程实现
|
||||
* 实现 RemoDispBus 协议:解析 remo_disp_server.py 的请求,返回 lcd 显存数据等。
|
||||
* 帧格式: [TAG][cmd][len_hi][len_lo][data][crc],CRC = data 区逐字节异或低 8 位。
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Drv/lcd.h"
|
||||
#include "Drv/lcd/lcd.h"
|
||||
#include "TCP/tcp.h"
|
||||
#include "Drv/key.h"
|
||||
#include "remoteDisplay.h"
|
||||
@@ -78,6 +78,7 @@ static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int
|
||||
/* 处理 CMD_LCDMEM:请求 data 为 4 字节大端起始地址;回复 [4B 地址][显存数据] */
|
||||
static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len)
|
||||
{
|
||||
const uint8_t *framebuffer = Lcd_GetFrameBuffer();
|
||||
unsigned int start_addr = 0;
|
||||
|
||||
if (req_len >= 4)
|
||||
@@ -96,7 +97,7 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
|
||||
{
|
||||
unsigned int offset = start_addr;
|
||||
copy_len = LCD_DISPLAYMEMORYSIZE - offset;
|
||||
memcpy(payload + 4, g_tCVsr.pwbyLCDMemory + offset, copy_len);
|
||||
memcpy(payload + 4, framebuffer + offset, copy_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user