2026-07-15 15:57:37 +08:00
|
|
|
|
#include "lcd.h"
|
|
|
|
|
|
#include "lcd_font.h"
|
|
|
|
|
|
#include "../App/config.h"
|
|
|
|
|
|
#include "intrins.h"
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// ====== RM69310 Truly AMOLED 屏幕 SPI 引脚映射 (8051 bit 寻址控制) ======
|
|
|
|
|
|
sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
|
|
|
|
|
|
sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
|
|
|
|
|
|
sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
|
|
|
|
|
|
sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
|
|
|
|
|
|
sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// ====== SGM3833 升压/负压三路 OLED 屏电源 PMIC 使能引脚 ======
|
|
|
|
|
|
sbit SGM_CTRL = P3^4; // 单线脉冲通信控制线
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 引用外部延时函数 (定义在 system.c 中)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
extern void Delay_ms(u16 ms);
|
|
|
|
|
|
extern void Delay10us(void);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 通过脉冲序列为 SGM3833 屏幕电源管理芯片写入开启配置
|
|
|
|
|
|
* @param pulse_cnt 要写入的脉冲个数 (本系统传入 27 次脉冲开启正负双电轨)
|
|
|
|
|
|
* @details SGM3833 采用单线边沿脉冲计数协议进行数字控制。
|
|
|
|
|
|
* - tOFF 必须大于 5ms 才能完全复位芯片进入指令模式。
|
|
|
|
|
|
* - 每次电平拉低低电平持续 10us 后拉高,形成一个脉冲计数值。
|
|
|
|
|
|
* - 发送完毕后需拉高并维持 5ms 以上,以便将计数值存入寄存器,锁定目标电压轨开启。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void SGM_SendPulse(u8 pulse_cnt)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 i;
|
|
|
|
|
|
SGM_CTRL = 0;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
Delay_ms(5); // 确保电源芯片进入关断/复位状态 (tOFF)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
SGM_CTRL = 1;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
Delay_ms(5); // 确保进入初始化状态 (tINIT >= 300us)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(i = 0; i < pulse_cnt; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SGM_CTRL = 0;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
Delay10us(); // 保持低电平 10us
|
2026-07-15 15:57:37 +08:00
|
|
|
|
SGM_CTRL = 1;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
Delay10us(); // 保持高电平 10us
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
SGM_CTRL = 1;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
Delay_ms(5); // 确保数据稳定存储锁定 (tSTORE >= 80us)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 软件模拟 8 位串行 SPI 字节数据发送
|
|
|
|
|
|
* @param dat 待发送的 1 字节数据
|
|
|
|
|
|
* @details 采用典型的 bit-banging 模式:时钟 SCL 在发送前置低,将数据 MSB 依次提取后赋值给
|
|
|
|
|
|
* 数据线 SDI,随后拉高 SCL 完成一个 bit 的沿采样,循环 8 次发送完成。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void SPI_SendData(u8 dat)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 i;
|
|
|
|
|
|
for(i = 0; i < 8; i++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_SCL = 0; // 拉低时钟,准备更新数据
|
2026-07-15 15:57:37 +08:00
|
|
|
|
if(dat & 0x80)
|
|
|
|
|
|
LCD_SDI = 1;
|
|
|
|
|
|
else
|
|
|
|
|
|
LCD_SDI = 0;
|
|
|
|
|
|
_nop_();
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_SCL = 1; // 拉高时钟,触发屏幕在上升沿采样数据位
|
2026-07-15 15:57:37 +08:00
|
|
|
|
_nop_();
|
2026-07-16 17:52:52 +08:00
|
|
|
|
dat <<= 1; // 左移一位,准备发送下一位
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向屏幕控制器写入单字节控制指令 (DCX=0)
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void WriteComm(u8 cmd)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_CS = 0; // 开启片选
|
|
|
|
|
|
LCD_DCX = 0; // 切换为指令传输模式
|
2026-07-15 15:57:37 +08:00
|
|
|
|
SPI_SendData(cmd);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_CS = 1; // 关闭片选
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向屏幕控制器写入单字节显示数据 (DCX=1)
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void WriteData(u8 dat)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_CS = 0; // 开启片选
|
|
|
|
|
|
LCD_DCX = 1; // 切换为数据传输模式
|
2026-07-15 15:57:37 +08:00
|
|
|
|
SPI_SendData(dat);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_CS = 1; // 关闭片选
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 按照 RM69310 物理时序执行屏幕硬件复位
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_Reset(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
LCD_RST = 1;
|
|
|
|
|
|
Delay_ms(10);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_RST = 0; // 拉低复位引脚进入硬件复位状态
|
2026-07-15 15:57:37 +08:00
|
|
|
|
Delay_ms(20);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_RST = 1; // 重新拉高引脚
|
|
|
|
|
|
Delay_ms(120); // 必须等待至少 120ms 才能执行后续的初始化
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
|
|
|
|
|
|
* @details 先配置控制 GPIO 为推挽输出,接着根据 Truly 信利原厂提供的初始化指令序列,
|
|
|
|
|
|
* 完成偏置电压、刷新周期、Gamma 参数、跳帧逻辑及色彩空间模式等一系列初始化工作。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_Init(void)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 1. 配置 P1 端口的 LCD_RST/DCX/SDI/SCL/CS 引脚全部为推挽输出模式 (M1=0, M0=1)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
|
|
|
|
|
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 2. 配置 P3.4 (SGM_CTRL) 为推挽输出模式
|
2026-07-15 15:57:37 +08:00
|
|
|
|
P3M1 &= ~(1 << 4);
|
|
|
|
|
|
P3M0 |= (1 << 4);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 设置各控制引脚初始默认电平
|
2026-07-15 15:57:37 +08:00
|
|
|
|
SGM_CTRL = 1;
|
|
|
|
|
|
LCD_CS = 1;
|
|
|
|
|
|
LCD_SCL = 1;
|
|
|
|
|
|
LCD_RST = 1;
|
|
|
|
|
|
LCD_DCX = 1;
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_Reset(); // 硬件复位屏幕
|
|
|
|
|
|
|
|
|
|
|
|
// ====== 写入 RM69310 原厂寄存器初始化指令序列 ======
|
|
|
|
|
|
WriteComm(0xFE);WriteData(0x01); // 切换至扩展 Page 1 寄存器页
|
|
|
|
|
|
WriteComm(0x04);WriteData(0xa0); // 开启 SPI 写入显存 RAM 允许
|
|
|
|
|
|
WriteComm(0x05);WriteData(0x70); // 配置屏幕尺寸规格为 128RGB
|
|
|
|
|
|
WriteComm(0x06);WriteData(0x3C); // 设定垂直有效扫描线 NL = 240
|
|
|
|
|
|
WriteComm(0x25);WriteData(0x06); // 正常模式伽马设定Gamma1
|
|
|
|
|
|
WriteComm(0x26);WriteData(0x80); // T1A 时序常数 = 280
|
|
|
|
|
|
WriteComm(0x27);WriteData(0x12); // 正常模式消隐后肩 VBP = 12
|
|
|
|
|
|
WriteComm(0x28);WriteData(0x12); // 正常模式消隐前肩 VFP = 8
|
|
|
|
|
|
WriteComm(0x2A);WriteData(0x06); // 空闲模式伽马设定 Gamma1
|
|
|
|
|
|
WriteComm(0x2B);WriteData(0x80); // T1B 时序常数 = 280
|
|
|
|
|
|
WriteComm(0x2D);WriteData(0x12); // 空闲消隐后肩 = 12
|
|
|
|
|
|
WriteComm(0x2F);WriteData(0x12); // 空闲消隐前肩 = 8
|
|
|
|
|
|
WriteComm(0x37);WriteData(0x0C); // 预充电控制电压 VGSP
|
|
|
|
|
|
WriteComm(0x6D);WriteData(0x18); // 关闭跳帧 VGMP 和 VGSP 稳压调节
|
|
|
|
|
|
WriteComm(0x29);WriteData(0x01); // 关闭正常模式的跳帧
|
|
|
|
|
|
WriteComm(0x30);WriteData(0x43); // 设定空闲模式跳帧使刷新频率降为 30Hz
|
|
|
|
|
|
WriteComm(0x0E);WriteData(0x83); // 正常模式 AVDD 电压 = 5.6V
|
|
|
|
|
|
WriteComm(0x0F);WriteData(0x83); // 空闲模式 AVDD = 5.6V
|
|
|
|
|
|
WriteComm(0x10);WriteData(0x71); // AVDD 三倍 VCI 升压控制
|
|
|
|
|
|
WriteComm(0x11);WriteData(0xb3); // VCL = -2倍 VCI 产生负压
|
|
|
|
|
|
WriteComm(0x12);WriteData(0xb3);
|
|
|
|
|
|
WriteComm(0x13);WriteData(0x80); // 正常模式下 VGH 等于 AVDD
|
|
|
|
|
|
WriteComm(0x14);WriteData(0x80); // 空闲模式下 VGH 等于 AVDD
|
|
|
|
|
|
WriteComm(0x15);WriteData(0x81); // 正常模式下低侧门电压 VGL = VCL - VCI
|
|
|
|
|
|
WriteComm(0x16);WriteData(0x81);
|
|
|
|
|
|
WriteComm(0x18);WriteData(0x66); // 偏置电平 VGHR = 6V
|
|
|
|
|
|
WriteComm(0x19);WriteData(0x44); // 偏置电平 VGLR = -6V
|
|
|
|
|
|
WriteComm(0x1E);WriteData(0x02); // 开启电荷平衡 EQ 调制
|
|
|
|
|
|
WriteComm(0x5B);WriteData(0x10); // 启用参考负压 VREFN5
|
|
|
|
|
|
WriteComm(0x62);WriteData(0x19); // 正常模式 VREFN5 电平 = -3V
|
|
|
|
|
|
WriteComm(0x63);WriteData(0x19); // 空闲模式 VREFN5 = -3V
|
|
|
|
|
|
WriteComm(0x70);WriteData(0x55); // 关闭显示时源极驱动器至 AVDD
|
|
|
|
|
|
WriteComm(0x1D);WriteData(0x02); // 启用 EQ 平衡
|
|
|
|
|
|
WriteComm(0x89);WriteData(0x18); // 控制偏置 VGMP = 5.5V
|
|
|
|
|
|
WriteComm(0x8A);WriteData(0xb9); // 控制偏置 VGSP = 2V
|
|
|
|
|
|
WriteComm(0x8B);WriteData(0x01); // 偏置高字节
|
|
|
|
|
|
WriteComm(0x8C);WriteData(0x10); // VGMP 设为 5.4V
|
|
|
|
|
|
WriteComm(0x8D);WriteData(0x90); // VGSP 设为 2V
|
|
|
|
|
|
WriteComm(0x8E);WriteData(0x01);
|
|
|
|
|
|
WriteComm(0x6E);WriteData(0x0A); // 关闭 MIPI 高速数据接口以选择 SPI 驱动方式
|
|
|
|
|
|
WriteComm(0x6A);WriteData(0x05); // 配置 MUX 选择器
|
|
|
|
|
|
WriteComm(0x3A);WriteData(0x08); // 行延迟常数 T1_sd
|
|
|
|
|
|
WriteComm(0x3B);WriteData(0x00); // 帧延迟常数 Tp_sd
|
|
|
|
|
|
WriteComm(0x3D);WriteData(0x16); // 保持延迟常数 Th_sd
|
|
|
|
|
|
WriteComm(0x3F);WriteData(0x27); // 切换时序 Tsw_sd
|
|
|
|
|
|
WriteComm(0x40);WriteData(0x0F); // 切换保持 Thsw_sd
|
|
|
|
|
|
WriteComm(0x41);WriteData(0x0D); // 切换释放 Thsd_sd
|
|
|
|
|
|
|
|
|
|
|
|
// 如下配置模拟通道多路选通时序
|
|
|
|
|
|
WriteComm(0x42);WriteData(0x14);
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x43);WriteData(0x41);
|
|
|
|
|
|
WriteComm(0x44);WriteData(0x25);
|
|
|
|
|
|
WriteComm(0x45);WriteData(0x52);
|
|
|
|
|
|
WriteComm(0x46);WriteData(0x36);
|
|
|
|
|
|
WriteComm(0x47);WriteData(0x63);
|
|
|
|
|
|
WriteComm(0x48);WriteData(0x14);
|
|
|
|
|
|
WriteComm(0x49);WriteData(0x41);
|
|
|
|
|
|
WriteComm(0x4A);WriteData(0x25);
|
|
|
|
|
|
WriteComm(0x4B);WriteData(0x52);
|
|
|
|
|
|
WriteComm(0x4C);WriteData(0x36);
|
|
|
|
|
|
WriteComm(0x4D);WriteData(0x63);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x4E);WriteData(0x14);
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x4F);WriteData(0x41);
|
|
|
|
|
|
WriteComm(0x50);WriteData(0x25);
|
|
|
|
|
|
WriteComm(0x51);WriteData(0x52);
|
|
|
|
|
|
WriteComm(0x52);WriteData(0x36);
|
|
|
|
|
|
WriteComm(0x53);WriteData(0x63);
|
|
|
|
|
|
WriteComm(0x54);WriteData(0x14);
|
|
|
|
|
|
WriteComm(0x55);WriteData(0x41);
|
|
|
|
|
|
WriteComm(0x56);WriteData(0x25);
|
|
|
|
|
|
WriteComm(0x57);WriteData(0x52);
|
|
|
|
|
|
WriteComm(0x58);WriteData(0x36);
|
|
|
|
|
|
WriteComm(0x59);WriteData(0x63);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x66);WriteData(0x90); // 空闲模式使用内部集成供电
|
|
|
|
|
|
WriteComm(0x67);WriteData(0x40); // 内部延迟 1 帧切断电源
|
|
|
|
|
|
WriteComm(0x72);WriteData(0x1A); // 内部核心 OVDD 电压 = 4.6V
|
|
|
|
|
|
WriteComm(0x73);WriteData(0x07); // 内部核心 OVSS 电压 = -2V
|
|
|
|
|
|
WriteComm(0x74);WriteData(0x0C); // OVDD 的最终输入来源设为 AVDD
|
|
|
|
|
|
WriteComm(0x6A);WriteData(0x3D); // 向单线接口发送脉冲,配置升压
|
|
|
|
|
|
WriteComm(0x6B);WriteData(0x29);
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0xFE);WriteData(0x04); // 切换至 Page 4
|
|
|
|
|
|
WriteComm(0x5E);WriteData(0x01);
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x5F);WriteData(0xB8);
|
|
|
|
|
|
WriteComm(0x60);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x61);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x62);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x76);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x77);WriteData(0x3B);
|
|
|
|
|
|
WriteComm(0x78);WriteData(0x92);
|
|
|
|
|
|
WriteComm(0x79);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x7A);WriteData(0xBB);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x00);WriteData(0xDC); // 扫描时钟 CK1 寄存器配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x01);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x02);WriteData(0x02);
|
|
|
|
|
|
WriteComm(0x03);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x04);WriteData(0x08);
|
|
|
|
|
|
WriteComm(0x05);WriteData(0x01);
|
|
|
|
|
|
WriteComm(0x06);WriteData(0x70);
|
|
|
|
|
|
WriteComm(0x07);WriteData(0x0A);
|
|
|
|
|
|
WriteComm(0x08);WriteData(0x00);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x09);WriteData(0xDC); // 扫描时钟 CK2 寄存器配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x0a);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x0b);WriteData(0x02);
|
|
|
|
|
|
WriteComm(0x0C);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x0D);WriteData(0x08);
|
|
|
|
|
|
WriteComm(0x0E);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x0F);WriteData(0x70);
|
|
|
|
|
|
WriteComm(0x10);WriteData(0x0A);
|
|
|
|
|
|
WriteComm(0x11);WriteData(0x00);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x12);WriteData(0xCC); // 发光逻辑 EM_CK1 配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x13);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x14);WriteData(0x02);
|
|
|
|
|
|
WriteComm(0x15);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x16);WriteData(0x20);
|
|
|
|
|
|
WriteComm(0x17);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x18);WriteData(0x28);
|
|
|
|
|
|
WriteComm(0x19);WriteData(0x26);
|
|
|
|
|
|
WriteComm(0x1A);WriteData(0x00);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x1B);WriteData(0xCC); // 发光逻辑 EM_CK2 配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x1C);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x1D);WriteData(0x02);
|
|
|
|
|
|
WriteComm(0x1E);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x1F);WriteData(0x20);
|
|
|
|
|
|
WriteComm(0x20);WriteData(0x01);
|
|
|
|
|
|
WriteComm(0x21);WriteData(0x28);
|
|
|
|
|
|
WriteComm(0x22);WriteData(0x26);
|
|
|
|
|
|
WriteComm(0x23);WriteData(0x00);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x4C);WriteData(0x89); // 扫描起始信号 SN_STV 配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x4D);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x4E);WriteData(0x01);
|
|
|
|
|
|
WriteComm(0x4F);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x50);WriteData(0x01);
|
|
|
|
|
|
WriteComm(0x51);WriteData(0xBB);
|
|
|
|
|
|
WriteComm(0x52);WriteData(0xBB);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x53);WriteData(0xCA); // 发光起始信号 EM_STV 配置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x54);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x55);WriteData(0x03);
|
|
|
|
|
|
WriteComm(0x56);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x58);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0x59);WriteData(0x00);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x65);WriteData(0x45);
|
|
|
|
|
|
WriteComm(0x66);WriteData(0x0C);
|
|
|
|
|
|
WriteComm(0x67);WriteData(0x00);
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0xFE);WriteData(0x01); // 切换至 Page 1 验证芯片 ID
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0xE5);WriteData(0x00);
|
|
|
|
|
|
WriteComm(0xE6);WriteData(0x10);
|
|
|
|
|
|
WriteComm(0xE7);WriteData(0x31);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0xFE);WriteData(0x00); // 切换回 Page 0 进行正常显示操作
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0xC4);WriteData(0x80);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设定列像素物理边界 (4~123,宽120像素)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// ====== 显式设置屏幕像素色彩格式为 16 位像素格式 (COLMOD = 0x75,即RGB565格式) ======
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x3A);WriteData(0x75);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x11);WriteData(0x00); // 唤醒屏芯片:发送 Sleep Out 退出睡眠指令
|
|
|
|
|
|
Delay_ms(150); // 退出睡眠状态后,必须等待 150ms 以便驱动电荷泵稳定
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x29);WriteData(0x00); // 开启屏幕显示:发送 Display ON 指令,像素层开始发光
|
2026-07-15 15:57:37 +08:00
|
|
|
|
Delay_ms(20);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置屏幕绘制显存窗口区域
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x2A);
|
|
|
|
|
|
WriteData((u8)(x_start >> 8));
|
|
|
|
|
|
WriteData((u8)(x_start & 0xFF));
|
|
|
|
|
|
WriteData((u8)(x_end >> 8));
|
|
|
|
|
|
WriteData((u8)(x_end & 0xFF));
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 写入 Row Address Set 行地址设置寄存器命令 (0x2B)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x2B);
|
|
|
|
|
|
WriteData((u8)(y_start >> 8));
|
|
|
|
|
|
WriteData((u8)(y_start & 0xFF));
|
|
|
|
|
|
WriteData((u8)(y_end >> 8));
|
|
|
|
|
|
WriteData((u8)(y_end & 0xFF));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 用指定色彩刷写清空全屏幕
|
|
|
|
|
|
* @param color 16位 RGB565 颜色填充值
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_Clear(u16 color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u16 i, j;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 【物理引脚偏移说明】:
|
|
|
|
|
|
// 该 AMOLED 屏在物理层面上水平方向的列像素地址为 4 ~ 123(总宽 120 像素)。
|
|
|
|
|
|
// 因此调用 LCD_SetWindow 时,X 轴的物理起始和终止坐标必须偏置 +4 才能与物理屏幕像素完全对齐。
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_SetWindow(4, 123, 0, 239);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(i = 0; i < 120; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for(j = 0; j < 240; j++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteData((u8)(color >> 8)); // 发送 RGB565 高 8 位字节
|
|
|
|
|
|
WriteData((u8)(color & 0xFF)); // 发送 RGB565 低 8 位字节
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在屏幕指定视口绘制实心矩形
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u16 i, count;
|
|
|
|
|
|
u8 ch = (u8)(color >> 8);
|
|
|
|
|
|
u8 cl = (u8)(color & 0xFF);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
|
|
|
|
|
|
// X 坐标应用物理偏置偏移 +4 像素
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteComm(0x2C); // 写入显存
|
|
|
|
|
|
count = w * h; // 实心矩形包含的总像素数
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(i = 0; i < count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteData(ch);
|
|
|
|
|
|
WriteData(cl);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 绘制空心矩形边界框
|
|
|
|
|
|
* @details 通过组合调用四次 LCD_FillRect 实心线来绘制矩形的上、下、左、右四条边。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_FillRect(x, y, w, 1, color); // 上边框线 (厚度 1 像素)
|
|
|
|
|
|
LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
|
|
|
|
|
|
LCD_FillRect(x, y, 1, h, color); // 左边框线
|
|
|
|
|
|
LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 渲染显示一个单色字模位图资源
|
|
|
|
|
|
* @param x 渲染起始坐标 X
|
|
|
|
|
|
* @param y 渲染起始坐标 Y
|
|
|
|
|
|
* @param w 位图宽度 (单位: 像素点数)
|
|
|
|
|
|
* @param h 位图高度 (单位: 像素点数)
|
|
|
|
|
|
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色,0表示画背景色)
|
|
|
|
|
|
* @param color 16位前景色
|
|
|
|
|
|
* @param bg_color 16位背景色
|
|
|
|
|
|
* @details 会逐行、逐列解析单色图的字节数组,每一行占 (w+7)/8 个字节,
|
|
|
|
|
|
* 利用位操作提取指定 bit 的状态并发送色彩数据写入屏幕。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u16 r, c;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
u16 byte_width = (w + 7) / 8; // 计算在单色编码中,一行图像占用的实际字节数
|
2026-07-15 15:57:37 +08:00
|
|
|
|
u8 ch = (u8)(color >> 8);
|
|
|
|
|
|
u8 cl = (u8)(color & 0xFF);
|
|
|
|
|
|
u8 bgh = (u8)(bg_color >> 8);
|
|
|
|
|
|
u8 bgl = (u8)(bg_color & 0xFF);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1); // 限制绘制显存窗口并应用偏移
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x2C);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(r = 0; r < h; r++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for(c = 0; c < w; c++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
u16 byte_idx = r * byte_width + (c / 8); // 定位当前像素所在单色图数组中的字节索引
|
|
|
|
|
|
u8 bit_shift = 7 - (c % 8); // 定位该像素在该字节中对应的 bit 位置 (高位在前)
|
|
|
|
|
|
|
2026-07-15 15:57:37 +08:00
|
|
|
|
if(bmp[byte_idx] & (1 << bit_shift))
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteData(ch); // 该位为 1,发送前景色
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteData(cl);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
WriteData(bgh); // 该位为 0,发送背景色
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteData(bgl);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
|
|
|
|
|
|
* @param ch ASCII 字符本身
|
|
|
|
|
|
* @details 遍历 8x16 点阵字库,将索引对应的 16 个字符编码字节依次提取,按位发送。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 r, c;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
u8 char_idx = ch - 0x20; // 偏移计算:ASCII 可打印字符从 0x20 (空格) 开始
|
2026-07-15 15:57:37 +08:00
|
|
|
|
u8 f_h = (u8)(color >> 8);
|
|
|
|
|
|
u8 f_l = (u8)(color & 0xFF);
|
|
|
|
|
|
u8 b_h = (u8)(bg_color >> 8);
|
|
|
|
|
|
u8 b_l = (u8)(bg_color & 0xFF);
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
|
2026-07-15 15:57:37 +08:00
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1); // 应用 +4 像素偏移
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x2C);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 循环 16 行,绘制 8x16 字型
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(r = 0; r < 16; r++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
u8 line = FONT_8x16[char_idx][r]; // 提取当前行对应的 8 位单色像素信息
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(c = 0; c < 8; c++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 从高到低依次提取单行中的像素点
|
2026-07-15 15:57:37 +08:00
|
|
|
|
if(line & (0x80 >> c))
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteData(f_h);
|
|
|
|
|
|
WriteData(f_l);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteData(b_h);
|
|
|
|
|
|
WriteData(b_l);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
while(*str)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_ShowChar8x16(x, y, *str, color, bg_color); // 渲染当前字符
|
|
|
|
|
|
x += 8; // X 轴起始位置偏移 8 像素,准备绘制下一个字符
|
|
|
|
|
|
str++; // 移向下一个字符
|
2026-07-15 15:57:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u16 len = 0;
|
|
|
|
|
|
char *p = str;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
while(*p++) len++; // 计算字符串长度
|
|
|
|
|
|
|
2026-07-15 15:57:37 +08:00
|
|
|
|
if(len * 8 >= 120)
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 若超出或等同于屏幕有效宽度 (120像素),强制从最左侧 0 开始左对齐绘制
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_ShowString(0, y, str, color, bg_color);
|
|
|
|
|
|
else
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 否则通过公式 (120 - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
|
|
|
|
|
|
* @details 【字库双重放大技术】:
|
|
|
|
|
|
* 由于 8051 FLASH 容量有限,手环未附带特大型号的 16x32 大字库。
|
|
|
|
|
|
* 此函数通过算法对 8x16 基础字库点阵数据在水平方向(列)和垂直方向(行)各重复绘制 2 遍
|
|
|
|
|
|
* (即单个点扩充绘制为 2x2 的像素团),从而实现对原字型双重放大渲染为 16x32 规格,节省空间。
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 r, c, i, j;
|
|
|
|
|
|
u8 char_idx = ch - 0x20;
|
|
|
|
|
|
u8 f_h = (u8)(color >> 8);
|
|
|
|
|
|
u8 f_l = (u8)(color & 0xFF);
|
|
|
|
|
|
u8 b_h = (u8)(bg_color >> 8);
|
|
|
|
|
|
u8 b_l = (u8)(bg_color & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
if(ch < 0x20 || ch > 0x7E) return;
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
|
2026-07-15 15:57:37 +08:00
|
|
|
|
WriteComm(0x2C);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 遍历字型的 16 个点阵行数据
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(r = 0; r < 16; r++)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 line = FONT_8x16[char_idx][r];
|
2026-07-16 17:52:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 纵向插点翻倍:将当前行重复绘制两次 (i = 0 和 i = 1)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(i = 0; i < 2; i++)
|
|
|
|
|
|
{
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 遍历单行内的 8 个 bit 像素点
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(c = 0; c < 8; c++)
|
|
|
|
|
|
{
|
|
|
|
|
|
u8 val_bit = (line & (0x80 >> c));
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 横向插点翻倍:单个像素点重复发送写入 2 次 (j = 0 和 j = 1)
|
2026-07-15 15:57:37 +08:00
|
|
|
|
for(j = 0; j < 2; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(val_bit)
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteData(f_h);
|
|
|
|
|
|
WriteData(f_l);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteData(b_h);
|
|
|
|
|
|
WriteData(b_l);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字符串
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
while(*str)
|
|
|
|
|
|
{
|
|
|
|
|
|
LCD_ShowChar16x32(x, y, *str, color, bg_color);
|
2026-07-16 17:52:52 +08:00
|
|
|
|
x += 16; // 每次起始坐标偏移 16 像素
|
2026-07-15 15:57:37 +08:00
|
|
|
|
str++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 17:52:52 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
|
|
|
|
|
|
*/
|
2026-07-15 15:57:37 +08:00
|
|
|
|
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
u16 len = 0;
|
|
|
|
|
|
char *p = str;
|
2026-07-16 17:52:52 +08:00
|
|
|
|
while(*p++) len++; // 计算字数
|
|
|
|
|
|
|
2026-07-15 15:57:37 +08:00
|
|
|
|
if(len * 16 >= 120)
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 超出屏幕宽度,左对齐
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_ShowString16x32(0, y, str, color, bg_color);
|
|
|
|
|
|
else
|
2026-07-16 17:52:52 +08:00
|
|
|
|
// 水平居中计算
|
2026-07-15 15:57:37 +08:00
|
|
|
|
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
|
|
|
|
|
|
}
|