2026-07-09 10:49:15 +08:00
|
|
|
|
# 编码实现 - LCD 显示与字模驱动 (Docs/60_coding/mod-lcd.md)
|
2026-07-09 10:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
本文件对应 LCD 驱动与字模渲染的具体编码实现细节。
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 对应代码源文件
|
|
|
|
|
|
|
|
|
|
|
|
* [Drivers/lcd.c](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.c) (LCD 初始化、SPI 数据写、画图画字函数)
|
|
|
|
|
|
* [Drivers/lcd.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.h) (管脚接口与画图画字声明)
|
|
|
|
|
|
* [Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) (ASCII 8x16 点阵字模与图标数据)
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 关键代码片段与逻辑实现
|
|
|
|
|
|
|
2026-07-09 14:03:01 +08:00
|
|
|
|
### 2.1 局部窗口设置 (X/Y轴偏移量修正)
|
|
|
|
|
|
在 `lcd.c` 中配置窗口时,需要将坐标对齐到 ST7789V 在 135x240 面板下的实际 GRAM 范围,加入 X 轴 52 像素、Y 轴 40 像素的偏差:
|
2026-07-09 10:31:36 +08:00
|
|
|
|
```c
|
|
|
|
|
|
void LCD_SetWindow(u16 x1, u16 y1, u16 x2, u16 y2) {
|
2026-07-09 14:03:01 +08:00
|
|
|
|
x1 += 52;
|
|
|
|
|
|
x2 += 52;
|
|
|
|
|
|
y1 += 40;
|
|
|
|
|
|
y2 += 40;
|
|
|
|
|
|
|
2026-07-09 10:31:36 +08:00
|
|
|
|
LCD_WriteCmd(0x2A); // Column Address Set
|
|
|
|
|
|
LCD_WriteData(x1 >> 8);
|
|
|
|
|
|
LCD_WriteData(x1 & 0xFF);
|
|
|
|
|
|
LCD_WriteData(x2 >> 8);
|
|
|
|
|
|
LCD_WriteData(x2 & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
LCD_WriteCmd(0x2B); // Row Address Set
|
|
|
|
|
|
LCD_WriteData(y1 >> 8);
|
|
|
|
|
|
LCD_WriteData(y1 & 0xFF);
|
|
|
|
|
|
LCD_WriteData(y2 >> 8);
|
|
|
|
|
|
LCD_WriteData(y2 & 0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-09 14:03:01 +08:00
|
|
|
|
### 2.2 电池图标与动态百分比 (包含充电 + 后缀) 渲染
|
|
|
|
|
|
在 `main.c` 的时钟页面刷新中,获取当前百分比及充电状态,动态绘制到状态栏:
|
2026-07-09 10:31:36 +08:00
|
|
|
|
```c
|
2026-07-09 14:03:01 +08:00
|
|
|
|
void UI_ShowClockPage(SystemState state, u8 hour, u8 min) {
|
|
|
|
|
|
char bat_str[10];
|
|
|
|
|
|
u8 bat_val = Read_Battery_Percent();
|
|
|
|
|
|
bit charging = Is_Charging();
|
|
|
|
|
|
|
|
|
|
|
|
LCD_Clear(COLOR_BLACK);
|
|
|
|
|
|
|
|
|
|
|
|
// 充电状态判定与百分比字符串格式化
|
|
|
|
|
|
if (charging) {
|
|
|
|
|
|
// 充电时显示 + 号后缀,如 "85%+"
|
|
|
|
|
|
if (bat_val >= 100) {
|
|
|
|
|
|
bat_str[0] = '1'; bat_str[1] = '0'; bat_str[2] = '0'; bat_str[3] = '%'; bat_str[4] = '+'; bat_str[5] = '\0';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bat_str[0] = '0' + (bat_val / 10);
|
|
|
|
|
|
bat_str[1] = '0' + (bat_val % 10);
|
|
|
|
|
|
bat_str[2] = '%';
|
|
|
|
|
|
bat_str[3] = '+';
|
|
|
|
|
|
bat_str[4] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
// X 坐标向左平移 8 像素 (X=69) 以确保和电池图标 (X=107) 之间有 6px 以上的安全间距
|
|
|
|
|
|
LCD_ShowString(69, 24, bat_str, COLOR_GRAY, COLOR_BLACK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未充电正常显示百分比,如 "85%"
|
|
|
|
|
|
if (bat_val >= 100) {
|
|
|
|
|
|
bat_str[0] = '1'; bat_str[1] = '0'; bat_str[2] = '0'; bat_str[3] = '%'; bat_str[4] = '\0';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bat_str[0] = '0' + (bat_val / 10);
|
|
|
|
|
|
bat_str[1] = '0' + (bat_val % 10);
|
|
|
|
|
|
bat_str[2] = '%';
|
|
|
|
|
|
bat_str[3] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
LCD_ShowString(77, 24, bat_str, COLOR_GRAY, COLOR_BLACK);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制电池位图图标
|
|
|
|
|
|
LCD_ShowImage(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
2026-07-09 10:31:36 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 字模百分号偏置
|
|
|
|
|
|
在 `lcd_font.h` 的 ASCII 字符数组中,百分号 `%`(十进制 37,十六进制 0x25)的字模通过在字模绘制时向上平移 2 像素对齐普通数字的下基准线。
|
2026-07-09 10:49:15 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.4 水平居中渲染实现
|
|
|
|
|
|
在 `lcd.c` 中,字符串居中函数需修改宽度限制参数为 `135` 像素:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color) {
|
|
|
|
|
|
u16 len = 0;
|
|
|
|
|
|
char *p = str;
|
|
|
|
|
|
while(*p++) len++;
|
|
|
|
|
|
if(len * 8 >= 135)
|
|
|
|
|
|
LCD_ShowString(0, y, str, color, bg_color);
|
|
|
|
|
|
else
|
|
|
|
|
|
LCD_ShowString((135 - len * 8) / 2, y, str, color, bg_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-07-09 11:03:18 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.5 绑定界面 ID 渲染实现
|
|
|
|
|
|
在 `main.c` 中通过 `FormatHex` 接口将 `bracelet_factory_id` 转换为十六进制字符串,并显示在绑定界面的底部:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void UI_ShowBindingPage(void) {
|
|
|
|
|
|
char id_str[15];
|
|
|
|
|
|
// 渲染背景与图标
|
|
|
|
|
|
// ...
|
|
|
|
|
|
// 绘制手环自身的出厂 ID
|
|
|
|
|
|
FormatHex(bracelet_factory_id, id_str);
|
|
|
|
|
|
LCD_ShowStringCentered(180, id_str, COLOR_GRAY, COLOR_BLACK);
|
|
|
|
|
|
}
|
2026-07-09 11:32:00 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.6 对码确认与序号微调页渲染
|
|
|
|
|
|
在 `main.c` 中通过以下方式渲染可供按键微调的数字后缀界面:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void UI_ShowPairConfirmPage(u8 type, u8 selected_suffix) {
|
|
|
|
|
|
char num_buf[10];
|
|
|
|
|
|
|
|
|
|
|
|
LCD_Clear(COLOR_BLACK);
|
|
|
|
|
|
LCD_ShowStringCentered(40, "v RECU", COLOR_GREEN, COLOR_BLACK);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据类型渲染 32x32 图标
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
|
|
// 居中显示预设名称前缀
|
|
|
|
|
|
LCD_ShowStringCentered(135, sensor_prefixes[type], COLOR_WHITE, COLOR_BLACK);
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接成带选框的 "[ 02 ]" 格式字符串并高亮居中渲染
|
|
|
|
|
|
num_buf[0] = '[';
|
|
|
|
|
|
num_buf[1] = ' ';
|
|
|
|
|
|
num_buf[2] = ' ';
|
|
|
|
|
|
num_buf[3] = '0' + (selected_suffix / 10);
|
|
|
|
|
|
num_buf[4] = '0' + (selected_suffix % 10);
|
|
|
|
|
|
num_buf[5] = ' ';
|
|
|
|
|
|
num_buf[6] = ' ';
|
|
|
|
|
|
num_buf[7] = ']';
|
|
|
|
|
|
num_buf[8] = '\0';
|
|
|
|
|
|
LCD_ShowStringCentered(165, num_buf, COLOR_CYAN, COLOR_BLACK);
|
|
|
|
|
|
}
|
2026-07-09 13:06:01 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Checked and verified with always-on screen and sleep removal changes -->
|
2026-07-09 11:32:00 +08:00
|
|
|
|
```
|
2026-07-09 11:03:18 +08:00
|
|
|
|
```
|