Linux 内核 auxdisplay 辅助显示子系统:ks0108 控制器与 cfag12864b LCD 驱动实战指南
2026/9/7 16:19:06 网站建设 项目流程

Linux 内核 auxdisplay 辅助显示子系统:ks0108 控制器与 cfag12864b LCD 驱动实战指南

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

本文围绕 Linux 内核Documentation/admin-guide/auxdisplay/目录下的官方文档展开,系统讲解辅助显示(auxdisplay)子系统的工作原理:从 ks0108 LCD 控制器的硬件特性、并行口接线,到 cfag12864b LCD 的 framebuffer 暴露方式与用户态编程方法,并结合当前仓库中的 Kconfig 选项、内核驱动源码与官方示例程序,给出从内核配置、接线到用户态点屏的完整可复现路径。

1. 辅助显示子系统总览:它解决什么问题

“辅助显示”(Auxiliary Display)指主显示器之外的小尺寸显示设备,比如并口(打印机并行口)上挂接的字符/点阵 LCD。这类设备不是内存映射(memory-mapped)外设,内核必须用软件驱动逐字节驱动它,因此被单独归入drivers/auxdisplay/子系统统一管理。

当前仓库中的文档入口是 auxdisplay/index.rst,它把该子系统的文档组织为一个索引页,下辖两个子文档:

  • ks0108.rst:Samsung KS0108 LCD 控制器驱动文档;
  • cfag12864b.rst:Crystalfontz 12864b 系列 LCD 驱动文档。

两者构成“控制器 + 具体屏”的两层结构:ks0108 是可复用的 LCD 控制器驱动(负责并行口协议),cfag12864b 是基于该控制器的具体 LCD 板卡驱动(负责把 framebuffer 数据按页分发到两块控制器)。这个分层关系在源码中一一对应:

  • 控制器驱动:ks0108.c
  • 板卡驱动:cfag12864b.c
  • framebuffer 层:cfag12864bfb.c
  • 官方用户态示例:samples/auxdisplay/cfag12864b-example.c

整个子系统通过 Kconfig 菜单AUXDISPLAY("Auxiliary display support")统一开关,位于 drivers/auxdisplay/Kconfig。

2. ks0108 控制器:设备特性与内存模型

ks0108 文档(ks0108.rst)声明该驱动支持 Samsung KS0108 LCD 控制器,License 为 GPLv2。其设备信息表如下,这是理解后续所有驱动行为的硬件基础:

属性
制造商Samsung
设备名称KS0108 LCD Controller
设备代码ks0108
类型LCD 控制器(Liquid Crystal Display Controller)
宽度 / 高度64 × 64 像素
颜色2 色(黑白 B/N)
页数(Pages)8 页
每页地址数64 个
每个地址的数据量1 字节
显存总量8 × 64 × 1 = 512 字节

由此得到 KS0108 的核心内存模型:控制器管理一块 64×64 的位图,组织为8 个“页”(page),每页 64 个字节地址,每个地址 1 字节代表该页垂直方向 8 个像素(每 bit 一个像素)。单块控制器的总容量为 512 字节。要寻址任意像素,需要先选中“页”,再选中页内“列地址”,这与文档中“Addresses: 64 each page / Data size: 1 byte each address”的描述一致。

文档还说明了接线能力:该驱动支持数据并行端口(data parallel port)接线。官方文档特别提醒,如果你不是在自研 LCD 硬件,应当查看具体屏的接线文档,并直接举了 cfag12864b 的例子——这也解释了为什么文档目录里两个文件并列存在。

3. 内核配置:Kconfig 选项、默认值与模块参数

3.1 配置项一览

drivers/auxdisplay/Kconfig 中 ks0108 / cfag12864b 相关的配置项及其含义如下:

配置项类型默认值依赖 / 说明
AUXDISPLAYmenuconfig,总开关"Auxiliary display support",关闭则整个子菜单不出现
KS0108tristate(Y/M/N)"KS0108 LCD Controller",depends on PARPORT_PC;选 M 时模块名为ks0108。注意:它只是控制器,还需要上层具体的 LCD 驱动
KS0108_PORThex0x378LCD 所连并行口地址。第一并行口标准地址 0x378,第二 0x278,第三 0x3BC
KS0108_DELAYint2每次写并行口控制线之间的延时(微秒)。若 LCD 出现随机丢写,应调大
CFAG12864Btristate"CFAG12864B LCD",depends on X86depends on FBdepends on KS0108select FB_SYSMEM_HELPERS;选 M 时产生两个模块:cfag12864bcfag12864bfb
CFAG12864B_RATEint20刷新率(Hz)

几个使用要点来自 Kconfig help 文本,值得逐条注意:

  1. KS0108_PORT 的确认方法:如果不确定并行口地址,先加载parport模块,再执行dmesgcat /proc/ioports,即可看到系统上存在几个并行口及各自地址;“通常你只需要 0x378”。

  2. 编译为模块后仍可用模块参数覆盖KS0108_PORTKS0108_DELAY的 help 均注明,若编译为 M,可在 insmod 时通过模块参数覆盖默认值。这与源码一致——ks0108.c 中用module_param()注册了两个运行时参数,初值分别取自CONFIG_KS0108_PORTCONFIG_KS0108_DELAY

    static unsigned int ks0108_port = CONFIG_KS0108_PORT; module_param(ks0108_port, uint, 0444); MODULE_PARM_DESC(ks0108_port, "Parallel port where the LCD is connected"); static unsigned int ks0108_delay = CONFIG_KS0108_DELAY; module_param(ks0108_delay, uint, 0444); MODULE_PARM_DESC(ks0108_delay, "Delay between each control writing (microseconds)");

    因此实际加载时可写成insmod ks0108.ko ks0108_port=0x378 ks0108_delay=5这类形式(参数名、类型与权限0444均由源码确认)。

  3. CFAG12864B_RATE 的风险警告:由于 LCD 不是内存映射设备,驱动必须用软件搬运数据,help 文本明确提示:设得过高可能让系统变慢甚至“冻结计算机”;如果 CPU 很慢或感觉系统被拖慢,应调低该值。

  4. CFAG12864B 的平台限制:help 文本写明它依赖 x86 架构与 framebuffer 支持;该 LCD framebuffer 可以挂接虚拟控制台(console)且工作正常,但不能挂接 Xorg 的 fbdev 驱动。

3.2 推荐的配置组合

要启用 cfag12864b 显示,从源码结构看依赖链是CFAG12864B → KS0108 → PARPORT_PC,且需要FB。在make menuconfig中对应的操作是:

  • Device Drivers → Character device support → Parallel port support(勾选PARPORT_PC,或加载现成的 parport 模块)
  • Device Drivers → Auxiliary display support(AUXDISPLAY=y
    • KS0108 LCD Controller(KS0108=yM
    • 并行口地址(KS0108_PORT,默认 0x378)
    • 控制线写延时(KS0108_DELAY,默认 2us)
    • CFAG12864B LCD(CFAG12864B=yM
    • 刷新率(CFAG12864B_RATE,默认 20Hz)

4. ks0108 控制器驱动:并行口协议与命令封装

ks0108 文档只声明了设备特性,具体的协议行为要读源码。ks0108.c 通过parportAPI 操作并行口,并对外导出了一组“命令”函数,供上层 LCD 驱动(如 cfag12864b.c)调用。

4.1 两个最底层的原语

void ks0108_writedata(unsigned char byte) { parport_write_data(ks0108_parport, byte); } void ks0108_writecontrol(unsigned char byte) { udelay(ks0108_delay); parport_write_control(ks0108_parport, byte ^ (bit(0) | bit(1) | bit(3))); }

两个要点:

  • 控制线位反相:源码注释解释,ks0108_writecontrol的位需要先对 bit(0)、bit(1)、bit(3) 取反(^ (bit(0) | bit(1) | bit(3))),因为并行口的硬件逻辑门本身会对这些信号做一次“非”运算。这是硬件层面的取反补偿,调试时如果 Enable / RS / R/W 行为反直觉,应首先想到这一点。
  • ks0108_delay在这里生效:每次写控制线前执行udelay(ks0108_delay),这正是 Kconfig 中KS0108_DELAY(“Delay between each control writing (microseconds)”)对应的运行时行为。出现随机丢写时调大该值即作用于此处。

4.2 页/地址/显示状态命令

与文档“8 页 × 64 地址 × 1 字节”内存模型直接对应的封装函数:

void ks0108_displaystate(unsigned char state) /* 开/关显示 */ { ks0108_writedata((state ? bit(0) : 0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5)); } void ks0108_startline(unsigned char startline) /* 设置起始扫描行(0..63) */ { ks0108_writedata(min_t(unsigned char, startline, 63) | bit(6) | bit(7)); } void ks0108_address(unsigned char address) /* 设置页内列地址(0..63) */ { ks0108_writedata(min_t(unsigned char, address, 63) | bit(6)); } void ks0108_page(unsigned char page) /* 设置页(0..7) */ { ks0108_writedata(min_t(unsigned char, page, 7) | bit(3) | bit(4) | bit(5) | bit(7)); }

(以上为 ks0108.c 摘录。)可以看到每个命令都是向数据口写一个“指令字节”:用特定标志位的组合区分命令类型(显示状态 / 起始行 / 列地址 / 页),其余位携带参数。源码注释还特别说明了一个重要的并发约定:

这些导出的命令函数“不锁”(don't lock)——上层的顶层驱动应该负责加锁。因为逐字节加锁“又慢又没意义”,上层应保证调用序列本身不会竞争。

4.3 并发模型:parport + pardevice

驱动通过parport_find_base(ks0108_port)按 Kconfig 里配置的基地址查找并行口,再注册一个pardevice(见 ks0108.c 中的ks0108_parport/ks0108_pardevice)。这解释了 Kconfig help 中“依赖并行口支持(Depends on Parallel Port support)”的来由:ks0108 复用内核并行口子系统,不自己摸端口。

5. cfag12864b:双控制器、并行口接线与显存布局

5.1 设备信息

cfag12864b 文档(cfag12864b.rst)描述 Crystalfontz 12864b 系列 LCD:

属性
制造商Crystalfontz
设备名称Crystalfontz 12864b LCD Series
设备代码cfag12864b
类型LCD(Liquid Crystal Display)
宽度 / 高度128 × 64 像素
颜色2 色(黑白 B/N)
控制器ks0108
控制器数量2 个
每控制器页数8 页
每页地址数64 个
每地址数据量1 字节
显存总量2 × 8 × 64 × 1 = 1024 字节 = 1 Kbyte

关键结构是**“128×64 的屏 = 两块 64×64 的 KS0108 显存拼接”**:每块控制器负责 64 列,两块并排放出 128 列,所以总容量正好是 1 Kbyte。文档给出厂商主页为 crystalfontz.com(12864b 产品页)。

5.2 并行口接线(官方文档原图)

文档强调 cfag12864b 系列没有官方接线图,社区通用做法是接到并行口,原文档给出的完整接线如下(保持原样,供硬件接线参考):

Parallel Port cfag12864b Name Pin# Pin# Name Strobe ( 1)------------------------------(17) Enable Data 0 ( 2)------------------------------( 4) Data 0 Data 1 ( 3)------------------------------( 5) Data 1 Data 2 ( 4)------------------------------( 6) Data 2 Data 3 ( 5)------------------------------( 7) Data 3 Data 4 ( 6)------------------------------( 8) Data 4 Data 5 ( 7)------------------------------( 9) Data 5 Data 6 ( 8)------------------------------(10) Data 6 Data 7 ( 9)------------------------------(11) Data 7 (10) [+5v]---( 1) Vdd (11) [GND]---( 2) Ground (12) [+5v]---(14) Reset (13) [GND]---(15) Read / Write Line (14)------------------------------(13) Controller Select 1 (15) Init (16)------------------------------(12) Controller Select 2 Select (17)------------------------------(16) Data / Instruction Ground (18)---[GND] [+5v]---(19) LED + Ground (19)---[GND] Ground (20)---[GND] E A Values: Ground (21)---[GND] [GND]---[P1]---(18) Vee - R = Resistor = 22 ohm Ground (22)---[GND] | - P1 = Preset = 10 Kohm Ground (23)---[GND] ---- S ------( 3) V0 - P2 = Preset = 1 Kohm Ground (24)---[GND] | | Ground (25)---[GND] [GND]---[P2]---[R]---(20) LED -

从接线图可以读出几个硬件语义:

  • 并行口Strobe接 LCD 的EnableSelectData/Instruction(即 RS 信号);Line(pin 14)Init(pin 16)分别接Controller Select 1/2——这正是两块 KS0108 的选择线,印证了“双控制器”结构;
  • Read/Write 固定接地(只写不读)、Reset 接 +5V(不复位);
  • 背光 LED 由 +5V 经 1K 可调电位器 P2、22 欧电阻 R 到 LED- 构成限流回路,Vee 偏压由 10K 电位器 P1 分压提供——这是调节液晶对比度的典型接法。

接线前提与 Kconfig 的depends on PARPORT_PC以及 help 文本中的cat /proc/ioports提示一致:这块屏本质上就是一个“挂在并口上的软件显存”。

5.3 板级驱动 cfag12864b.c 的角色

板卡驱动 cfag12864b.c 位于两个驱动之间:向下调用 ks0108 的命令函数把 1 Kbyte 的 framebuffer 缓冲区按“页 × 列”分发到两块控制器,向上通过 platform device 与cfag12864bfb层交互,并按CFAG12864B_RATE(默认 20Hz)周期性刷新。Kconfig 中“选 M 时会得到cfag12864bcfag12864bfb两个模块”的说法与之对应:板卡驱动与 framebuffer 驱动分开成模块。

6. cfag12864bfb:framebuffer 暴露方式(/dev/fbX 从哪来)

文档“Userspace Programming”一节的结论——“cfag12864bfb 描述一个 framebuffer 设备(/dev/fbX)”——其实现见 cfag12864bfb.c:

  • 固定屏幕参数cfag12864bfb_fixtype = FB_TYPE_PACKED_PIXELSvisual = FB_VISUAL_MONO10(1bpp 单色),line_length = CFAG12864B_WIDTH / 8(128/8 = 16 字节每行);
  • 可变屏幕参数cfag12864bfb_varxres = 128yres = 64bits_per_pixel = 1,RGB 分量各为{ 0, 1, 0 }(1 位色深);
  • cfag12864bfb_probe()info->flags = FBINFO_VIRTFBinfo->screen_buffer = cfag12864b_bufferinfo->screen_size = CFAG12864B_SIZE,随后register_framebuffer(info)完成注册。

也就是说 framebuffer 的显存就是内核里一块静态缓冲区cfag12864b_buffer(大小CFAG12864B_SIZE= 1024 字节),FBINFO_VIRTFB表明它使用虚拟 framebuffer 模式:用户态写入的数据先落到该缓冲区,由cfag12864b板级驱动按刷新率搬运到硬件。这正好解释了文档中的那句关键提示:

你可以像普通文件一样使用 framebuffer(fopen、fwrite、fclose……),但在下一次刷新时刻到来之前,LCD 不会更新。(Also, you can use the framebuffer as a file... Although the LCD won't get updated until the next refresh time arrives.)

mmap 路径同样有源码依据:cfag12864bfb_mmap()通过virt_to_page(cfag12864b_buffer)+vm_map_pages_zero()把这 1024 字节映射进用户进程地址空间(见 cfag12864bfb.c),因此文档才说“open & mmap … 对大多数用途是最佳选项”。

7. 用户态编程:1 Kbyte 位图模型与官方完整示例

7.1 位图模型

文档明确:

  • framebuffer 大小为1024 字节 = 1 Kbyte
  • 每一位(bit)代表一个像素,bit 为 1 时点亮,为 0 时熄灭;
  • 可用文件方式读写,也可以 mmap,推荐后者;
  • 完整的用户态示例见samples/auxdisplay/cfag12864b-example.c

7.2 官方示例程序解读

samples/auxdisplay/cfag12864b-example.c 是一个可直接编译运行的完整演示程序(GPL-2.0),其骨架如下:

初始化与退出——open + mmap 1024 字节:

static int cfag12864b_init(char *path) { cfag12864b_fd = open(path, O_RDWR); if (cfag12864b_fd == -1) return -1; cfag12864b_mem = mmap(0, CFAG12864B_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, cfag12864b_fd, 0); if (cfag12864b_mem == MAP_FAILED) { close(cfag12864b_fd); return -2; } return 0; }

位图地址计算——128×64 屏按 8 像素一字节打包:

#define CFAG12864B_ADDRESS(x, y) ((y) * CFAG12864B_WIDTH / \ CFAG12864B_BPB + (x) / CFAG12864B_BPB) #define CFAG12864B_BIT(n) (((unsigned char) 1) << (n))

即像素 (x, y) 落在第y行的第x/8个字节中,字节内位编号为x % 8

单像素操作——set / unset / isset / not:

static void cfag12864b_set(unsigned char x, unsigned char y) { if (CFAG12864B_CHECK(x, y)) cfag12864b_buffer[CFAG12864B_ADDRESS(x, y)] |= CFAG12864B_BIT(x % CFAG12864B_BPB); } static void cfag12864b_unset(unsigned char x, unsigned char y) { if (CFAG12864B_CHECK(x, y)) cfag12864b_buffer[CFAG12864B_ADDRESS(x, y)] &= ~CFAG12864B_BIT(x % CFAG12864B_BPB); }

边界检查由CFAG12864B_CHECK(x, y)宏完成,源码中默认关闭(#undef CFAG12864B_DOCHECK时恒为真),性能优先;也可整屏填充/清空(cfag12864b_fill()全 0xFF、cfag12864b_clear()全 0),或用cfag12864b_format(matrix)把一张 128×64 的 0/1 矩阵批量打包进位图。

提交显示——注意“双缓冲 + 显式 blit”的模式:所有绘图只改本地cfag12864b_buffer,最后由cfag12864b_blit()一次性memcpy到 mmap 的内核帧缓冲:

static void cfag12864b_blit(void) { memcpy(cfag12864b_mem, cfag12864b_buffer, CFAG12864B_SIZE); }

main 流程——程序依次演示 6 个例子,每个例子 blit 后等待回车:

int main(int argc, char *argv[]) { ... if (argc != 2) { printf( "Syntax: %s fbdev\n" "Usually: /dev/fb0, /dev/fb1...\n", argv[0]); return -1; } if (cfag12864b_init(argv[1])) { printf("Can't init %s fbdev\n", argv[1]); return -2; } for (n = 1; n <= EXAMPLES; n) { example(n); cfag12864b_blit(); while (getchar() != '\n'); } cfag12864b_exit(); return 0; }

6 个演示(example()的 switch 分支)分别是:1) 隔点打点(set 位)画点阵;2) 清屏;3) 通过 128×64 矩阵 format 画横线;4) 全屏填充;5) 用 unset 位画竖线;6) 全像素取反(not)。

7.3 运行方式

程序用法(由源码中打印的 Syntax 提示确认):第一个参数是 framebuffer 设备节点,通常是/dev/fb0/dev/fb1等。即在内核启用CFAG12864B并接线正确的前提下:

./cfag12864b-example /dev/fb0

哪个/dev/fbX对应 cfag12864bfb 取决于系统中 framebuffer 的注册顺序,可通过cat /sys/class/graphics/fbX/name查看(其fix.id在源码中固定为"cfag12864b",见 cfag12864bfb.c)。示例程序在 samples/auxdisplay/Makefile 中定义了构建规则,可用内核 sample 构建流程(make samples)编译。

8. 常见问题的定位路径

结合文档与源码,调试这类“并口 LCD 不亮/显示错乱”问题时可按以下顺序排查:

  1. 并行口地址是否找对cat /proc/ioportsdmesg确认,必要时用模块参数ks0108_port=0xXXX覆盖(KconfigKS0108_PORThelp 文本推荐的排查手段)。
  2. 是否丢写:提高ks0108_delay(Kconfig 默认 2 微秒;Kconfig 原文提示“If your LCD seems to miss random writings, increment this”)。
  3. 刷新是否发生:LCD 内容停在旧画面属于预期行为(写入后等下一次刷新才上屏);若刷新过慢,检查CFAG12864B_RATE(默认 20Hz),同时注意调过高的风险(Kconfig 原文警告可能拖慢甚至冻结系统)。
  4. 接线核对:重点核对 Strobe→Enable、Select→Data/Instruction、Line/Init→Controller Select 1/2、R/W 接地、Reset 接 +5V,背光限流电阻(22 欧 + 1K 电位器)与 Vee 偏压(10K 电位器)。
  5. fb 节点对应关系FBINFO_VIRTFB语义决定了用户态写入的是内核缓冲区,上屏由cfag12864b模块周期完成;确认两个模块都加载(lsmod | grep -E 'ks0108|cfag12864b')。

9. 小结

  • 文档层Documentation/admin-guide/auxdisplay/下三份文档(index + ks0108 + cfag12864b)构成了“控制器特性 → 设备特性与接线 → 用户态编程”的完整知识链;
  • 驱动层ks0108.c用 parport API 封装并行口协议(含控制线位反相与ks0108_delay节流),cfag12864b.c把 1 Kbyte 帧缓冲分发到两块控制器并按 20Hz(可配)刷新,cfag12864bfb.c以 1bpp MONO10 虚拟 framebuffer 的形式将其注册为/dev/fbX
  • 用户层:官方示例 cfag12864b-example.c 展示了 open/mmap 初始化、按y*16 + x/8计算位图地址、位级 set/unset/format 绘图与 blit 提交流程,可作为二次开发的起点。

适用前提与限制:该方案面向x86 + 物理并口(parport)+ KS0108 系并行驱动 LCD的硬件组合;LCD 由软件刷新,刷新率设置需量力而行;cfag12864b 的 framebuffer 可挂虚拟控制台,但不可用于 Xorg fbdev。以上结论均可在当前仓库的Documentation/admin-guide/auxdisplay/drivers/auxdisplay/samples/auxdisplay/中逐一对照验证。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询