
做嵌入式显示这块儿256×64 的图形点阵 LCD 模组绝对算是一个经典又实用的存在。我最近正好在调一块型号为 CYW-B25664A 的模组分辨率是 256×64这种规格在工业仪表、医疗设备、以及各种需要自定义界面的控制器上非常常见。它不像段码屏那样只能固定显示符号也不像字符屏那样受限于预置字库你可以随意画点、画线、写汉字甚至刷新曲线整块屏就像一张可以反复涂写的纸。这篇文章我打算把从拿到模组到最终调通的完整过程拆开来讲包括接口怎么选、初始化怎么做、显存怎么映射以及我踩过的几个坑给同样准备上手这类屏的朋友一个直接的参考。1. 内容整体设计与思路拆解1.1 为什么 256×64 这个分辨率这么吃香先说分辨率本身。256×64 意味着横向有 256 个像素点纵向有 64 个点总像素点数是 16384 个。看起来不多但在实际产品里这个尺寸非常适合显示几行关键参数加一个简单图形。例如一台温控仪表可以顶部显示当前温度、目标温度中间画一条实时温度曲线底部再放几个状态图标完全够用。对比更常见的 128×64 屏幕256×64 的横向像素翻倍了但高度不变这意味着它更适合显示曲线和中文长句。举个生活化的例子128×64 就像一张窄条便签写个标题再画个表情就满了而 256×64 更像是横开的笔记本能写一整行诗句还能在旁边画个示意图。对开发者来说多出来的横向空间不是简单的容量扩充而是界面排布自由度的提升。从成本角度看这类模组通常使用成熟的 COG 或 COB 工艺批量采购价格并不高驱动也相对通用。所以很多产品经理在规划界面时会优先选择这种“宽度有余、高度够用”的配置既能满足复杂信息的展示又不会像 320×240 那样增加成本和功耗。这也解释了为什么 256×64 在市场上长久不衰。1.2 图形点阵模组的优势到底在哪图形点阵 LCD 的核心优势是“可控性”。段码屏每个笔段都是独立的你需要什么图标就要在开模时定好后期想改只能重新开模周期长成本高。字符类 LCD 虽然能显示字母数字但字体和大小都是固定的想画个柱状图或者自定义符号就非常麻烦。图形点阵屏则完全没有这些限制只要驱动芯片支持你可以把任意像素点亮或熄灭。这种灵活性带来的直接好处是一套硬件可以适配多个产品型号只需要升级固件就能改变显示界面。比如同一块模组在 A 产品上显示温度曲线在 B 产品上显示计数值和状态图标在 C 产品上又变成菜单系统代码不同而已硬件完全通用。这特别符合现在小批量、多品种的生产模式能大幅减少库存压力。另外图形点阵屏在抗干扰和稳定性方面也值得说一句。它靠外部控制器刷新显示内容不像段码屏需要持续扫描只要初始化好内部显存主控就可以去忙别的事情需要更新时再写一次数据就行。再加上工业级的模组通常有宽温工作范围CYW-B25664A 这类模组在 -20℃ 到 70℃ 的环境下都能正常工作这就让它成为户外设备、冷库监控等场景的常客。2. 核心细节解析与实操要点2.1 接口类型与选型判断拿到 CYW-B25664A 模组第一个要确认的就是接口类型。常见的有两种并行接口和串行接口。并行接口是数据线 D0-D7 加控制线读、写、片选、复位、寄存器选择等优点是传输速度快适合需要频繁刷新全屏或播放动画的场景缺点就是占用主控引脚多接线麻烦对走线要求也高。串行接口通常指 SPI 或 I2C引脚少适合主控资源紧张的项目但速度相对慢一些刷新大区域时会有延迟。我在用这块模组时根据主控的资源情况选择了 SPI 模式只用了 5 根线SCLK、SID数据输入、CS片选、A0寄存器选择、RST复位。记得把模组上的 PSB 引脚并串行模式选择拉低到地才能切换成串行模式。这个细节特别容易忽略很多人焊好线之后屏幕没反应排查半天发现是 PSB 没接对。还有一个容易踩的地方是逻辑电平。有些模组支持 3.3V 或 5V如果主控是 3.3V模组用的也是 3.3V那没问题。但如果你用的是 5V 主控就得分压或者确认模组是否兼容 5V 输入。CYW-B25664A 我实测下来逻辑电平在 3.3V 和 5V 之间都能跑但为了保险起见我还是加了电平转换避免长期高电平冲击损坏驱动芯片。2.2 驱动芯片的初始化流程几乎所有图形点阵 LCD 模组内部都有一颗控制驱动芯片负责接收主控指令并刷新屏幕。CYW-B25664A 使用的芯片我已经记不清具体型号了但初始化逻辑大同小异核心步骤无外乎复位、功能设置、清屏、设置显示开关。复位这一步硬件上就是拉低 RST 再拉高保持至少几个毫秒。软件初始化时要注意指令发送的时序通常需要等待内部晶振稳定。我习惯在复位后先延时 100ms再去写第一条指令。功能设置指令设置的是接口模式、图形或文本模式、以及显示方向。比如发送 0x30 是选择 8 位接口发送 0x36 可能是扩展指令集这时候可以开启图形模式。具体的指令数值必须查对应芯片的数据手册不同芯片差异很大不能想当然。清屏指令其实分两步先把显示开关关闭再往整个显存写入 0x00。很多新手会忘记关显示就清存结果一边清一边屏幕上出现杂乱的闪烁。正确的顺序应该是关闭显示 - 清显存 - 打开显示。这个过程在初始化代码里是一个固定套路我之后在实操章节会给出具体示例。2.3 显存映射与坐标计算原理图形点阵屏的显示原理本质上就是显存和屏幕像素的一一对应。CYW-B25664A 的显存被分成两页每页 8 像素高每页 256 字节。第一页负责第 0 到第 7 行第二页负责第 8 到第 15 行。不对这里我要纠正一下对于 64 行的高度实际是分成 8 个页面每页 8 行。也就是 Page0 管第 0-7 行Page1 管第 8-15 行以此类推一直到 Page7 管第 56-63 行。理解了这一点你就会明白为什么写一个任意坐标的点需要两步先根据 X 坐标设置横向地址再根据 Y 坐标算出在哪一页以及页内的位偏移。例如要画点 (100, 20)Y20 属于 Page2因为 20÷82 余 4页内偏移就是 bit4。然后读取该位置显存的原始值把 bit4 置 1再写回去。这就是经典的“读-改-写”操作如果直接用画点函数覆盖写入会清掉同一个字节里其他像素的点。这种映射关系是理解整个图形库的地基。很多开源库比如 u8g2 之所以能画文字和图形就是因为它内部做好了坐标到显存的换算。如果你是想自己写驱动或者移植轻量级图形库我建议先在纸上画一个 256×64 的网格标好页面号再对着数据手册的操作步骤走一遍思路会清晰很多。3. 实操过程与核心环节实现3.1 硬件连接与供电检查动手之前先列一个连接清单避免接线时手忙脚乱。下面是 CYW-B25664A 在 SPI 模式下的引脚连接示例模组引脚功能连接目标VSS电源地主控 GNDVDD逻辑电源3.3V 或 5VPSB并/串行选择接 GND 选择串行CS片选主控 GPIO 低电平有效SCLK串行时钟主控 SPI 时钟引脚SID串行数据输入主控 MOSI 引脚A0寄存器选择主控 GPIO高电平为数据低电平为指令RST复位主控 GPIO 或接 RC 电路BLA背光正极串联限流电阻到电源BLK背光负极接 GND供电要特别注意背光。背光 LED 通常不能直接接电源需要串一个 100 欧姆左右的电阻限流不然电流过大会烧毁背光。我习惯先把背光断开只接逻辑部分确认屏幕能初始化出内容之后再接背光测试亮度。这样即使逻辑有问题也不会因为背光常亮而误以为模块坏了。上电顺序也有讲究。先给模组供电稳定 100ms 之后主控再拉低复位引脚进行硬件复位。如果主控和模组同时上电有时候会出现复位时序竞争导致初始化失败。这和我以前用 OLED 屏幕的教训一样硬件的鲁棒性往往就体现在这些细微的延时上。3.2 初始化代码示例与注释我用 C 语言写一个最基础的初始化函数针对常见的 25664 控制器。假设我们通过 GPIO 模拟 SPI这样不受硬件 SPI 速率影响更容易排查问题。// 模拟 SPI 发送一个字节 void spi_write_byte(unsigned char byte) { for (int i 0; i 8; i) { SCLK_LOW(); // 拉低时钟 if (byte 0x80) // 高位在前 SID_HIGH(); else SID_LOW(); SCLK_HIGH(); // 拉高时钟完成一个 bit byte 1; } } // 向控制器写指令 void lcd_cmd(unsigned char cmd) { A0_LOW(); // A0 低电平表示指令 CS_LOW(); spi_write_byte(cmd); CS_HIGH(); delay_us(10); } // 向控制器写数据 void lcd_data(unsigned char dat) { A0_HIGH(); // A0 高电平表示数据 CS_LOW(); spi_write_byte(dat); CS_HIGH(); delay_us(10); } // 初始化函数 void lcd_init(void) { RST_HIGH(); delay_ms(20); RST_LOW(); delay_ms(20); RST_HIGH(); delay_ms(100); // 确保复位完成 lcd_cmd(0x30); // 基本指令集8位接口 lcd_cmd(0x30); // 重复发送确保稳定 lcd_cmd(0x0C); // 显示开光标关闪烁关 lcd_cmd(0x01); // 清除显示显存清空 delay_ms(2); lcd_cmd(0x06); // 光标右移模式地址自动加1 }这段代码里有两个点值得解释。第一0x30 为什么连续发送两次因为有些控制芯片在上电后需要经过一两条指令才能锁定接口模式重复发送是为了避免首次发送失败。第二0x01 清屏之后一定要延时等待内部清显存需要时间尤其在全屏写入 0 之后如果立即操作后续指令可能被控制器忽略导致后续显示乱码。3.3 绘制图形与文字的完整实现初始化之后我们做几个基础绘图函数。先是画一个点这是所有图形的基础。void lcd_set_pixel(unsigned char x, unsigned char y, unsigned char color) { if (x 256 || y 64) return; // 检查坐标范围 unsigned char page y / 8; unsigned char bit y % 8; lcd_cmd(0x80 | page); // 设置页地址 lcd_cmd(0x80 | (x / 8)); // 设置列地址高位 lcd_cmd(0x04 | (x % 8)); // 设置列地址低位 // 读显存 lcd_cmd(0x30); // 切到基本指令集 lcd_cmd(0xE0); // 读取模式开始 spi_write_byte(0xFF); // 读取时发送一个伪字节以产生时钟 unsigned char read_value receive_from_slave(); // 取决于驱动实现 lcd_cmd(0x30); // 关闭读取模式 if (color) { read_value | (0x01 bit); // 点亮该像素 } else { read_value ~(0x01 bit); // 熄灭该像素 } // 写回显存 lcd_cmd(0x80 | page); lcd_cmd(0x80 | (x / 8)); lcd_cmd(0x04 | (x % 8)); lcd_data(read_value); }读显存的部分在模拟 SPI 下比较复杂因为标准 SPI 是单向的而控制器要回传显存数据主控需要支持双向通信或额外用一根 MISO 线。如果只是画点画线可以跳过读操作直接读显存镜像。我的做法是在主控 RAM 里维护一个 256×64 的位图数组所有绘图操作都在这块内存里完成然后一次性整屏刷新。这其实就是“离屏渲染”的思想对 LCD 来说特别实用。原因在于 LCD 控制器的读写时序较慢而且读操作有不确定性不如自己维护显存靠谱。做法如下unsigned char lcd_buffer[256 * 64 / 8]; // 2048 字节 void set_pixel(unsigned char x, unsigned char y, unsigned char color) { unsigned char index (y / 8) * 256 x; unsigned char bit y % 8; if (color) { lcd_buffer[index] | (0x01 bit); } else { lcd_buffer[index] ~(0x01 bit); } } void flush_screen(void) { lcd_cmd(0x30); // 基本指令集 for (int page 0; page 8; page) { lcd_cmd(0x80 | page); // 选择页 lcd_cmd(0x80); // 列地址起始为0 lcd_cmd(0x04); // 列地址低位0 for (int col 0; col 256; col) { lcd_data(lcd_buffer[page * 256 col]); } } }注意这里的显存布局是 8 行 × 256 列每页 256 字节和控制器硬件的显存顺序完全对应。所以 flush_screen 时只需要连续写 2048 字节就能刷完整屏非常高效。有了这个缓冲区画线、画圆、显示文字都变成纯软件操作和 LCD 速度无关了。显示中文的原理也很简单取模工具把汉字转成点阵数组例如 16×16 点阵就是 32 字节然后按照坐标逐位写入缓冲区。我在实际项目中用了一个开源工具生成宋体 12、16、24 等多套字库配合简易滚屏函数界面效果不比彩屏差多少。4. 常见问题与排查技巧实录4.1 白屏或黑屏怎么判断是哪一侧的问题这是一切调试开始时的第一道坎。屏幕白屏通常意思是显存全部是 1而黑屏是显存全部是 0。按我的经验白屏更常见于初始化失败因为复位后显存是随机值如果控制器没有进入正确的图形模式就会导致显示全白或全黑。排查顺序我建议是这样的先量电压确保 VDD 和 GND 正常背光能亮不代表逻辑正常。然后用万用表量 A0、CS、SCLK 这几个引脚在初始化代码里设断点看看主控是否真的输出了波形。很多问题出在 GPIO 复用设置比如主控的 SPI 引脚被其他外设占用电磁干扰或虚焊导致信号不完整。如果波形正常但仍然白屏就去检查 PSB 引脚。我之前就遇到过模组默认是并行模式我忘了拉低 PSB结果怎么初始化屏幕都没反应。把这个引脚接好后再重新上电复位屏幕立刻正常显示。还有一个容易忽略的坑有些模组的 RST 引脚没有上拉电阻如果主控的 GPIO 在开机瞬间是浮空复位时序可能乱掉。解决方法是加一个 10k 电阻把 RST 上拉到 VDD或者改用简单 RC 复位电路确保上电时低电平持续 1ms 以上。4.2 显示乱码或者花屏大概率是初始化顺序问题屏幕上出现乱掉的字、神奇的线条或者一闪一闪的噪点多半不是硬件坏了而是控制器没有进入你预期的状态。我遇到最典型的一种情况是先写了数据后发指令而控制器把之前的数据误认为是指令导致地址指针飘移。这里强调一点每次操作完数据之后要确认 A0 已经回到正确的状态。如果 A0 长期保持高电平控制器就会把后续所有输入当作数据而不是指令整个屏幕就会变成灾难现场。调试工具加上逻辑分析仪是最好的伙伴手机拍下时序波形和手册对比就能定位。花屏还有一个常见原因是清屏后没有等待。控制器内部清整个显存需要一定时间不同芯片快则几百微秒慢则几毫秒。如果你在清屏指令发出后不到 1 毫秒就写入新数据数据可能被清屏操作覆盖或者地址复位没完成导致写入位置错乱。我的经验是清屏后至少延时 2 毫秒再继续其他操作就算控制器速度快这个延时也不会造成可感知的卡顿。4.3 显示偏移错位检查显存地址和扫描方向屏幕上显示的内容整体错位比如画面左移 8 像素或者上下页颠倒这通常不是初始化问题而是你的绘图函数与屏幕扫描方向不匹配。有些模组支持左右反向或上下反向指令设置里有专门的控制位。例如 CYW-B25664A 就有方向控制指令可以设置扫描方向但默认情况下是按 Page0 到 Page7 的顺序。如果你在显存缓冲里画图没问题但上屏后错位可以先试一个最简单的测试图案左上角画一个实心矩形宽 64 高 64。如果这个矩形出现在屏幕右上角说明左右方向翻转了如果出现在左下角说明上下方向翻转了。然后在初始化代码里加一条方向控制指令来调整。另外注意列地址的计算方式是每列 8 个点一字节所以 X 坐标 0 到 7 对应 Column Address 0 的 bit0 到 bit7。如果你用字节数组顺序直接刷屏但控制器列地址是高位在左那么屏幕上就会形成左右镜像。这种问题很难用肉眼看出来最好是在缓冲区里写一个非对称的图案比如字母 F然后检查方向和镜像状态一次就能定位。5. 工具选型与效率提升建议5.1 取模工具和字库选择开发 256×64 界面时取模工具是必不可少的。我常用的工具支持从 BMP 图片转换成点阵数组也可以输入汉字选择自动生成、横向取模、反向取模等选项。工具的选择直接影响数据传输格式比如字节内高位在前还是低位在前行扫描顺序是从上到下还是从下到上。这些参数必须和你的缓冲区代码完全一致否则就会出现镜像和错位。字库方面16×16 点阵是最常见的汉字尺寸显示效果清晰且占用空间不大。如果做小字号的英文和数字也可以用 8×16 的 ASCII 字库这样可以节省很多内存。对于一次显示大量数据的场景比如医疗波形优先使用固定宽度字体这样绘图函数能快速算出每个字符的位置避免逐字符测量宽度的开销。5.2 调试技巧点阵屏幕的自我测试在实际调图形库之前我强烈建议先写一个自测程序。做法是把屏幕分成四块分别填充不同深度的图案左上角画一个 64×16 的竖条纹右上角画一个 128×8 的横条纹左下角画满 0xAA 模式右下角画一个 1 像素边框的圆。这样几秒钟就能看出坐标映射是否准确、显存读写是否正确、边界处理是否到位。如果自测程序全部通过再引入你的业务数据。我调试时最喜欢用对角线测试从左上角到右下角画一条线斜率正好是 1/4。为什么用对角线因为它能同时暴露 X 方向、Y 方向和字节内位序的所有问题。一旦这条线在屏幕上平滑连续说明整个坐标系统基本正确后续的界面开发就很顺畅。另一个实用小技巧是用 main 函数的延时循环在屏幕上不停刷新一个递增计数器的值观察是否有闪烁。如果数值更新时屏幕出现闪烁那大概率是主控和模组之间的通信速度太慢或者是在每次更新时执行了不必要的全屏清屏。解决办法是只更新变化区域比如只写计数器的几个字符位置而不是全屏刷新这样既流畅又不闪。6. 项目后期扩展与个人心得CYW-B25664A 这类模组用熟了之后你会发现它其实是一块很灵活的“画板”。我后来在一个模拟仪表盘的项目里用它显示转速和油量的弧线进度条再叠加当前挡位的中文字符效果非常直观。因为分辨率够宽我还把报警列表和历史趋势曲线放在同一个屏幕上用户切换页签的动画我是通过快速交替两个缓冲实现的虽然屏幕刷新速度有限但配合合适的刷新节奏视觉上依然很顺滑。如果后续想加触摸功能可以考虑在模组表面贴合电阻触摸膜然后通过主控的 ADC 去读取触摸位置。不过要注意LCD 本身没有触摸接口需要自己处理坐标校准这又是一个独立的工程。相比之下如果能接受按键操作用户界面那么开发量会小很多也更能发挥这块屏的长处。我个人在实际操作中的体会是调试这类显示器不要急着写满功能先把最基础的画点、画线、刷新整屏跑顺所有的高级功能都是在这几个原子上叠加的。耐心检查每一个引脚和时序比反复更换代码更有用。最后再说一个小技巧如果条件允许给模组配一套带压框的外壳屏幕边缘用导电胶带处理好静电防护到位能稳定运行很久这个经验在工业现场特别受用。