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 相关的配置项及其含义如下:
| 配置项 | 类型 | 默认值 | 依赖 / 说明 |
|---|---|---|---|
AUXDISPLAY | menuconfig,总开关 | — | "Auxiliary display support",关闭则整个子菜单不出现 |
KS0108 | tristate(Y/M/N) | — | "KS0108 LCD Controller",depends on PARPORT_PC;选 M 时模块名为ks0108。注意:它只是控制器,还需要上层具体的 LCD 驱动 |
KS0108_PORT | hex | 0x378 | LCD 所连并行口地址。第一并行口标准地址 0x378,第二 0x278,第三 0x3BC |
KS0108_DELAY | int | 2 | 每次写并行口控制线之间的延时(微秒)。若 LCD 出现随机丢写,应调大 |
CFAG12864B | tristate | — | "CFAG12864B LCD",depends on X86、depends on FB、depends on KS0108,select FB_SYSMEM_HELPERS;选 M 时产生两个模块:cfag12864b与cfag12864bfb |
CFAG12864B_RATE | int | 20 | 刷新率(Hz) |
几个使用要点来自 Kconfig help 文本,值得逐条注意:
KS0108_PORT 的确认方法:如果不确定并行口地址,先加载
parport模块,再执行dmesg或cat /proc/ioports,即可看到系统上存在几个并行口及各自地址;“通常你只需要 0x378”。编译为模块后仍可用模块参数覆盖:
KS0108_PORT和KS0108_DELAY的 help 均注明,若编译为 M,可在 insmod 时通过模块参数覆盖默认值。这与源码一致——ks0108.c 中用module_param()注册了两个运行时参数,初值分别取自CONFIG_KS0108_PORT与CONFIG_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均由源码确认)。CFAG12864B_RATE 的风险警告:由于 LCD 不是内存映射设备,驱动必须用软件搬运数据,help 文本明确提示:设得过高可能让系统变慢甚至“冻结计算机”;如果 CPU 很慢或感觉系统被拖慢,应调低该值。
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=y或M) - 并行口地址(
KS0108_PORT,默认 0x378) - 控制线写延时(
KS0108_DELAY,默认 2us) - CFAG12864B LCD(
CFAG12864B=y或M) - 刷新率(
CFAG12864B_RATE,默认 20Hz)
- KS0108 LCD Controller(
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 的Enable;Select接Data/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 时会得到cfag12864b与cfag12864bfb两个模块”的说法与之对应:板卡驱动与 framebuffer 驱动分开成模块。
6. cfag12864bfb:framebuffer 暴露方式(/dev/fbX 从哪来)
文档“Userspace Programming”一节的结论——“cfag12864bfb 描述一个 framebuffer 设备(/dev/fbX)”——其实现见 cfag12864bfb.c:
- 固定屏幕参数
cfag12864bfb_fix:type = FB_TYPE_PACKED_PIXELS,visual = FB_VISUAL_MONO10(1bpp 单色),line_length = CFAG12864B_WIDTH / 8(128/8 = 16 字节每行); - 可变屏幕参数
cfag12864bfb_var:xres = 128、yres = 64、bits_per_pixel = 1,RGB 分量各为{ 0, 1, 0 }(1 位色深); cfag12864bfb_probe()中info->flags = FBINFO_VIRTFB,info->screen_buffer = cfag12864b_buffer、info->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 不亮/显示错乱”问题时可按以下顺序排查:
- 并行口地址是否找对:
cat /proc/ioports或dmesg确认,必要时用模块参数ks0108_port=0xXXX覆盖(KconfigKS0108_PORThelp 文本推荐的排查手段)。 - 是否丢写:提高
ks0108_delay(Kconfig 默认 2 微秒;Kconfig 原文提示“If your LCD seems to miss random writings, increment this”)。 - 刷新是否发生:LCD 内容停在旧画面属于预期行为(写入后等下一次刷新才上屏);若刷新过慢,检查
CFAG12864B_RATE(默认 20Hz),同时注意调过高的风险(Kconfig 原文警告可能拖慢甚至冻结系统)。 - 接线核对:重点核对 Strobe→Enable、Select→Data/Instruction、Line/Init→Controller Select 1/2、R/W 接地、Reset 接 +5V,背光限流电阻(22 欧 + 1K 电位器)与 Vee 偏压(10K 电位器)。
- 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),仅供参考