墨水屏驱动与串行通信

沿SPI命令、双RAM、BUSY和刷新波形解释显示链;以实际代码分析返回值、局刷字节对齐与官方流程差异。

版本与验证范围

静态源码/电路核对;用户历史反馈单独注明;本次未编译、烧录或测量

返回 00-项目导航 · 接着读 03-画布像素与文字显示 · 调用入口见 04-主程序与实时任务

本文依据 2026-10-25 对 moqian 驱动演示工程 的静态阅读。源码行号对应本次文件快照。工程配置目标为 esp32s3,sdkconfig 标注 ESP-IDF 5.1.2。未编译、未烧录、未连接实物;屏幕批次、驱动板版本、电平与实际时序仍需核对。相邻的 weixue 是另一个工程,只用于比较。

一、先把显示链路分成三个层次

画一个黑点,不是直接把某个 GPIO 拉低。主程序先修改 ESP32 的内存画布,驱动再把整幅或局部图像写进显示控制器的存储器,最后发送刷新命令,让控制器执行驱动波形。电泳粒子完成移动需要的时间,通常远长于传输图像的时间。因此,“数据已经发完”和“屏幕已经刷新完成”是两件事。

流程示意 按原 Mermaid 线性链重绘
  1. 主程序生成画面
  2. 单色画布
  3. 串行总线传输
  4. 控制器图像存储器
  5. 激活刷新波形
  6. 等待忙信号释放
  7. 观察实际画面
查看原始 Mermaid 源码
mermaid
flowchart LR
    A[主程序生成画面] --> B[单色画布]
    B --> C[串行总线传输]
    C --> D[控制器图像存储器]
    D --> E[激活刷新波形]
    E --> F[等待忙信号释放]
    F --> G[观察实际画面]

本文从第三步开始讲。前两步的位运算在 03-画布像素与文字显示。

二、工程如何配置串行总线

main/epd_ssd1683.c:17–25 把 CONFIG_EPD_* 映射为驱动使用的宏;327–392 的 epd_init() 配置控制 GPIO、初始化总线、添加设备并复位显示控制器。当前配置如下,表中是软件配置值,不代表已经核对实物接线。

信号或参数 当前配置 在显示链路中的作用
MOSI GPIO11 从主机发送命令、参数和图像位
SCK GPIO12 为串行数据提供时钟
CS GPIO18 低电平选择显示器件
DC GPIO17 低电平表示命令,高电平表示数据
RST GPIO16 复位显示控制器
BUSY GPIO7 控制器报告忙或空闲
总线编号 CONFIG_EPD_SPI_HOST=1 转换为 SPI2_HOST
时钟 10 MHz 驱动设定的传输频率,稳定性需实测

epd_init():355–369 设置 miso_io_num=-1,所以这里没有读取显示寄存器的数据通道;BUSY 是独立的状态 GPIO。.mode=0 对应时钟空闲低、在相应采样边沿读取数据。.spics_io_num=-1 表示驱动不自动控制片选,应用在每次发送前后手动操作 CS。官方对这些字段及总线初始化顺序的说明见 ESP-IDF 5.1.2 串行总线主机驱动文档。

Kconfig.projbuild:57–63 允许填写宽高,但 epd_ssd1683.h:17–18 又直接定义 EPD_WIDTH=400、EPD_HEIGHT=300,没有使用配置宏。因此,只在配置菜单修改分辨率不会同步改变当前驱动的画布大小和固定初始化参数。

三、命令、参数和图像为什么走同一根数据线

epd_write_cmd():124–136 先令 DC=0,再发送一个字节;epd_write_data():138–150 令 DC=1 后发送一个字节。epd_write_data_buf():152–164 则发送一段连续字节。控制器依靠 DC 判断这些位的含义,同样的 0x24,在命令阶段和图像阶段含义不同。

下面是缓冲区发送函数的关键原文:

c
spi_transaction_t t = {
    .length = len * 8,
    .tx_buffer = data,
};
spi_device_transmit(spi_handle, &t);

这里 len 是字节数,而 spi_transaction_t.length 的单位是位。发送 50 字节的一行图像,必须填写 400 位;发送 15000 字节的一帧,必须填写 120000 位。如果错写 .length=len,只会发送预期数据量的八分之一。

算一个理想传输下限:15000×8÷10000000=0.012 s,即 12 毫秒。这只是图像位在 10 MHz 时钟下的传输时间,没有计入命令、调度、单字节事务开销和显示刷新。当前全刷还通过循环给第二块存储器发送 15000 次单字节事务,不能把 12 毫秒直接当作实际接口耗时或刷新耗时。

四、忙信号等待的真实行为

epd_wait_busy():167–186 把高电平解释为忙、低电平解释为空闲。这与微雪官方 EPD_4in2_V2.c 的等待逻辑相同。但实物仍应确认是对应的控制器和模块版本,不能套用其他屏幕的忙信号极性。

c
while (gpio_get_level(PIN_BUSY) == 1) {
    vTaskDelay(pdMS_TO_TICKS(10));
    timeout++;
    if (timeout > 500) {
        ESP_LOGE(TAG, "BUSY timeout! Pin stuck HIGH (timeout=%dms)", timeout * 10);
        break;
    }
}

这段代码做了三件事:每次等待大约一个调度节拍、重新采样忙信号、计数超限后跳出。timeout>500 意味着要数到 501,日志按 501×10=5010 毫秒计算;它不是用单调时钟测量得到的精确五秒。调度延迟会使实际时间更长。

尤其要读函数返回类型:它是 void。超时只打印日志并 break,没有向上层返回 ESP_ERR_TIMEOUT。因此,epd_init():388–392 即使遇到忙信号超时,仍会执行成功日志并返回 ESP_OK。三个 SPI 发送函数也没有检查 spi_device_transmit() 的返回值。主程序的“刷新完成”日志只能证明程序走到了那里,不能证明器件响应或画面正确。

五、窗口寻址和两块图像存储器

控制器写窗口由 epd_set_window():203–217 设置。横向地址的单位是字节,代码将 x_start、x_end 除以 8;纵向以行为单位,拆成低字节和高字节发送。对 400×300 画面而言,横向地址范围是 0~49,纵向像素行是 0~299。

需要区分 ESP32 的 frame_buf 和控制器内部的两块存储器:

操作入口 当前 moqian 实际行为 阅读时应保留的问题
epd_display_full():410–428 将图像写入 0x24,将全零写入 0x26 第二块存储器不能仅按宏名认定为实屏红色通道
epd_display_fast():430–446 同样写图像和全零,但装载另一组波形 画面质量、速度和残影尚未实测
epd_display_partial():448–469 设置窗口,仅向 0x24 写对应行片段 没有旧帧参数,也没有在此同步 0x26

代码将 0x26 命名为 SSD1683_WRITE_RED_RAM。这是命令命名,不能据此说当前黑白屏支持红色显示。在不同显示模式和波形下,两块存储器还可能参与新旧画面比较。微雪官方黑白显示函数会把同一幅图像分别写入 0x24 和 0x26;weixue/components/ssd1683/ssd1683.c:373–415 也采用这种做法,其局刷函数 527–609 进一步使用旧帧与新帧。两者与当前 moqian 明显不同,不能把对照工程的逻辑写成此工程已有功能。

六、三种刷新名称与底层命令要一起看

epd_init_full()、epd_init_fast()、epd_init_partial() 分别装载 lut_full、lut_fast、lut_partial。波形表决定驱动电场随时间的变化,不能当成普通图片查找表。表是否适合实物屏幕批次,需要厂商资料或实测依据。

三个公开刷新函数最后都调用 epd_turn_on_display():294–303:先向 0x22 写 0xC7,再发 0x20 激活,并等待忙信号。微雪官方对应源文件则将全刷、快刷、局刷激活值分别写为 0xF7、0xC7、0xFF。这是可直接核对的实现差异,不能仅凭 full、fast、partial 函数名就认定三种模式已符合厂商流程。源码注释中的约 3 秒、1.5 秒和 0.42 秒也不是本次测量结果。

另外,epd_init_common():249–251 的注释声称纵向递减,而写入的是 0x03;epd_write_bw_ram():308 从最后一行设置游标。应把数据进入模式、窗口端点、游标与发送行序放在一起核对,不能直接把这条注释当作扫描方向的证据。

七、从真实调用找到局部更新的对齐问题

epd_ssd1683.h:65–67 要求横坐标和宽度为 8 的倍数,因为传输以整个字节为单位。可是 main.c:147 定义 (80,100,240,100) 后,177 传入内部区域 (81,101,238,98)。驱动实际使用:

c
uint16_t src_offset = ((y + row) * EPD_WIDTH + x) / 8;
epd_write_data_buf(&image[src_offset], w / 8);

首行偏移是 (101×400+81)/8=5060,余下 1 位被截去;宽度 238/8=29 字节,只覆盖 232 位。而窗口横向从 81/8=10 到 318/8=39,覆盖 30 字节。窗口与实际每行字节数不一致,是静态可见的问题证据;具体出现错行、缺列还是其他现象,需要上板观察。本文没有修复源码。

八、可验证练习与常见误区

  1. 纸面跟踪一次全刷。 从 main.c:61 开始,列出复位、装载波形、写 0x24、写 0x26、激活、等待六个环节,给每个环节写出函数名。验收:能指出画布传输和电泳刷新各自发生在哪里。
  2. 计算合法局刷窗口。 对齐示例 (80,100,240,100) 每行 30 字节,共 3000 字节;首行从偏移 5010 开始。再与实际 (81,101,238,98) 比较。验收:能解释为什么简单整数除法不能保留起始像素的位偏移。
  3. 阅读错误路径。 假设忙信号持续为高,推演初始化最终返回值和日志顺序。验收:明确区分总线初始化错误、忙信号超时、画面错误三类故障。
  4. 后续实物练习,尚未执行。 确认模块版本、电平、共地和接线后,用逻辑分析仪观察 CS/DC/SCK/MOSI/BUSY,验证命令与数据阶段、帧长度和忙信号极性。先测量,再决定是否修改频率或刷新流程。

常见误区是把传输速率当刷新速率、把第二块存储器当作“没有红色所以随便清零”、把超时日志当错误已上报、把函数名当硬件验证结果。当前教学重点是看清每一层的数据和返回值,而不是先背下寄存器列表。

九、官方参考

来源与关联索引
来源
嵌入式比赛
原笔记
项目/墨嵌墨水屏/知识讲解/02-墨水屏驱动与串行通信.md
资料性质
实践指南。原文的版本、来源与验证边界继续保留。
关联笔记 5
返回文章目录

想去哪里?

22 个入口

首页作品作品展厅全部项目文章总览文件夹标签资料归档关于认识 Lan探索路线工具与工作台最近在做InkFeed · 4.2 英寸电子纸信息终端VIBRATION · 柔性振动检测与采集链路TRACE · 航向保持与灰度循迹小车STRIDE · 八自由度四足机器人SCOPE · 固件日志浏览器温度为什么被 AI 改了?FIFO 有半帧,就一定是故障吗?直线守航向,弧线再看黑线。

Tab 选择入口 · Enter 打开 · Esc 关闭