第一次拿到LCD12864这块小屏幕时,我盯着那密密麻麻的引脚有点发懵。后来才发现,这玩意儿就像个"电子画板",能显示128x64个像素点,内置了8192个汉字和128个ASCII字符,根本不需要自己造轮子。常见的型号有16脚和20脚两种,但核心引脚功能基本一致。
串行模式最吸引我的是接线简单——只需要5根线就能搞定!对比并行模式动辄十几根线的连接方式,串行模式简直是嵌入式开发的福音。实际项目中,当PCB空间紧张或者MCU引脚资源有限时,这个优势就特别明显。
屏幕的供电也很友好,3.3V-5V都能工作。记得我第一次调试时,用杜邦线随便接了个5V电源,屏幕背光就亮起来了,那一刻真的有种"通电即成功"的错觉。不过后来发现,亮度调节引脚V0接个10K电位器才是正经用法,否则在强光下根本看不清显示内容。
经过多次项目实战,我把串行模式的核心引脚总结为"五虎上将":
特别提醒:背光引脚A和K如果接反了,屏幕虽然能工作但背光不亮,这个问题我排查了整整一个下午!
实际接线时有个小技巧:用不同颜色的杜邦线区分功能。我的习惯是:
这种接线方式在调试时特别有用,当屏幕不显示时,先检查彩色线缆的连接顺序,能快速定位问题。曾经有个学生把CLK和SID接反了,导致显示乱码,用这个方法三分钟就解决了问题。
使用STM32CubeMX创建工程时,我建议先做这三件事:
以STM32F407为例,我的典型配置是:
配置时钟树时有个坑要注意:如果SPI时钟设得太高,会导致屏幕无法正常响应。我一般先用默认的84MHz主频,等显示正常后再尝试优化。
CubeMX生成的代码需要添加几个关键宏定义:
c复制#define CS_0 HAL_GPIO_WritePin(GPIOA, LCD_CS_Pin, GPIO_PIN_RESET)
#define CS_1 HAL_GPIO_WritePin(GPIOA, LCD_CS_Pin, GPIO_PIN_SET)
// 类似定义其他引脚的控制宏
这些宏会让后续的驱动代码更易读。比如写命令时就可以这样用:
c复制CS_0;
RS_0;
SendByte(0x2F); // 发送升压命令
CS_1;
最关键的WriteToLCD函数我优化过好几个版本,最终稳定运行的代码如下:
c复制void WriteToLCD(uint8_t data, uint8_t is_cmd) {
uint8_t i;
CS_0;
is_cmd ? RS_0 : RS_1;
for(i=0; i<8; i++) {
SCLK_0;
HAL_Delay(1); // 适当延时保证稳定性
(data & 0x80) ? SDA_1 : SDA_0;
SCLK_1;
data <<= 1;
}
}
这个函数有两个实用技巧:
初始化过程就像教婴儿学步,必须按固定步骤来:
常见坑点:升压步骤不能省略,否则显示会很淡。我有次偷懒跳过这步,结果屏幕显示几乎看不见,还以为买到了劣质屏。
显示ASCII字符时,本质上是查表绘图。内置字库已经帮我们做好了8x16的点阵数据,比如显示字母"A":
c复制void ShowChar(uint8_t x, uint8_t y, char ch) {
uint16_t index = (ch - 32) * 16; // ASCII码转索引
// 逐行写入点阵数据
for(uint8_t i=0; i<16; i++) {
SetPos(x, y+i);
WriteData(ascii_table_8x16[index + i]);
}
}
实际项目中,我封装了个更实用的字符串显示函数:
c复制void ShowString(uint8_t x, uint8_t y, char *str) {
while(*str) {
ShowChar(x, y, *str++);
x += 8; // 每个字符占8列
if(x >= 128) { // 自动换行
x = 0;
y += 16;
}
}
}
汉字显示要复杂些,因为每个汉字占16x16像素。内置字库的汉字是按GB2312编码排列的,显示"欢迎"二字:
c复制void ShowChinese(uint8_t x, uint8_t y, uint8_t index) {
for(uint8_t i=0; i<2; i++) { // 分上下两部分
SetPos(x, y+i);
for(uint8_t j=0; j<16; j++) { // 每部分16列
WriteData(Hzk[index*2+i][j]);
}
}
}
// 调用示例
ShowChinese(0, 0, 0); // 显示"欢"
ShowChinese(16, 0, 1); // 显示"迎"
有个实用技巧:提前建立汉字索引表,比如:
c复制typedef enum {
HANZI_HUAN = 0,
HANZI_YING = 1,
// 其他汉字...
} HANZI_INDEX;
这样代码可读性会更好,不用记晦涩的数字索引。
遇到屏幕不显示时,我的排查清单:
有个经典问题:显示内容上下颠倒。这是因为扫描方向设置错了,修改0xC0这个参数就能解决。
经过多次测试,我总结出几个优化点:
进阶技巧:利用DMA传输数据。虽然串行模式本身速度不快,但在需要频繁刷新时仍能提升性能。
除了内置字库,我们还能自定义图形。比如画个温度计图标:
c复制void DrawThermometer(uint8_t x, uint8_t y) {
static uint8_t icon[] = {
0x1C,0x22,0x22,0x22,0x22,0x22,0x22,0x1C, // 顶部圆形
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, // 柱状
0x1C,0x22,0x22,0x22,0x22,0x22,0x22,0x1C // 底部圆形
};
for(uint8_t i=0; i<sizeof(icon); i++) {
SetPos(x+i%8, y+i/8);
WriteData(icon[i]);
}
}
结合按键输入,可以做出交互式菜单。我的实现框架:
c复制typedef struct {
char *text;
void (*action)(void);
struct MenuItem *children;
} MenuItem;
MenuItem mainMenu[] = {
{"系统设置", NULL, settingsMenu},
{"参数查看", ShowParams, NULL},
// 其他菜单项...
};
通过递归调用就能实现无限级菜单,这在产品化项目中非常实用。