同一块主控,凭什么能配出三种不同的"身子"?
核心方法论:工欲善其事,必先利其器
「上一章我们把『车』(构建工具)造出来了,这章该把『方向盘』——板级配置——摸透了。同一颗 SF32 芯片,装到 V1.1、V1.2 上长得一模一样,可一旦接上 SPI 墨水屏,连引脚都得换一套。匠人手里的同一把凿子,换料就得换刀。把『哪块板、哪块屏、走哪个口』这条链路记牢,后面写驱动、改功能才不至于找错门。」
先看这张"家族照":仓库的 epdiy-epub/ 下躺着 三块开发板的板级配置,外加一份公共配置——sf32-oed-epd_v11(V1.1 板)、sf32-oed-epd_v12(V1.2 板)、sf32-oed-epd_v12_spi(V1.2 板、但墨水屏走 SPI 接口)、以及 sf32-oed-epd_base(V1.1 与 V1.2 共用的软件板级配置)。为什么同一颗 SF32LB52X 主控要配三套"身子"?因为硬件版本不同、屏幕接口不同:V1.1 与 V1.2 的 8-bit 并行墨水屏电路几乎一样,所以共用一份 base;而 v12_spi 换成了更小的 SPI 墨水屏,接口引脚完全不同,只能自成一派。
README 在「硬件连接」一节把话说得很直白:V1.1 与 V1.2 可共用 sf32-oed-epd_base 软件板级配置。这份 base 里藏着两样"公共资产":一是 Kconfig.board,定义了 8-bit 并行屏的信号引脚(D0-D7、CLK、LE、OE、SPH、STV、CPV、GMODE)、TPS 电源管理芯片的 I2C 引脚、触控芯片引脚与按键引脚;二是 bsp_*.c 这一批板级驱动(bsp_init.c 做时钟与启动、bsp_pinmux.c 做引脚复用、bsp_power.c 做上下电、bsp_lcd_tp.c 做屏与触控的电源管理)。v11 与 v12 的 Kconfig.board 都只有一行——把 base 引进来,剩下的引脚差异靠各自 hcpu/ 下的 Kconfig.board 与 board.conf 再补。
| 目标板 | 板级配置目录 | 是否共用 base | 屏幕接口 | 默认屏(Kconfig.proj) |
|---|---|---|---|---|
| V1.1 | sf32-oed-epd_v11 | 是(source "../sf32-oed-epd_base/Kconfig.board") | 8-bit 并行 | YZC085 1032×758 |
| V1.2 | sf32-oed-epd_v12 | 是(同上) | 8-bit 并行 | YZC085 1032×758 |
| V1.2 + SPI | sf32-oed-epd_v12_spi | 否(自带一套 bsp_*.c) | SPI 串行 | OPM037A3 240×416 |
# epdiy-epub/README.md · 「硬件连接」
- 将开发板与墨水屏通过对应连接器连接,注意排线方向。
- SF32-OED-EPD_V1.1 与 SF32-OED-EPD_V1.2 可共用 sf32-oed-epd_base 软件板级配置。
# sf32-oed-epd_v11/Kconfig.board —— 全部内容就这一行:把 base 引进来
source "../sf32-oed-epd_base/Kconfig.board"
# sf32-oed-epd_v12_spi/Kconfig.board —— 自带头文件,不引用 base
config ASIC
bool
default y
config EPD_SDA
int
default 5
config EPD_SCK
int
default 4
# ...(EPD_DC / EPD_RES / EPD_CS / EPD_BUSY 等 SPI 信号,后面 3.2 再展开)
判断"一块板能不能复用 base",只看一件事:它和 base 那块的 EPD 引脚是不是同一套。V1.1、V1.2 都是 8-bit 并行(D0-D7 + LE/OE/SPH/STV/CPV/GMODE),信号一致,所以共用;v12_spi 换成 SPI 的 SDA/SCK/DC/CS/RES/BUSY,引脚对不上,只能自己带一份 bsp_*.c。所以别被"同系列"骗了——接口变了,base 就变了。
翻开 sf32-oed-epd_v11/,它只有五个东西:Kconfig.board(引脚配置入口)、SConscript(该板源文件的编译入口)、hcpu/(HCPU,高性能核的配置)、lcpu/(LCPU,低功耗核的配置)、ptab.json(Flash 分区表,决定固件、文件系统、波形表各自烧在哪个地址)。hcpu/ 里是主核的配置四件套:board.conf(开启哪些外设驱动)、rtconfig.py(芯片型号 / 核 / 目标名)、Kconfig.board(定义 BSP_USING_BOARD_* 板宏并再次引用 base)、custom_mem_map.h(Flash 分区的 FAL 映射)。lcpu/ 结构类似,但内容极简。
最值得看的两处:hcpu 与 lcpu 的分工和lcpu 的"减法"。SF32LB52X 是双核 SoC——HCPU 跑应用(主循环、EPUB 解析、界面渲染都在它上面),LCPU 是个低功耗协处理器,系统休眠时用极小功耗盯着按键、定时器,把 HCPU 放回睡眠。所以 lcpu/board.conf 几乎全被注释掉了(没有 Finsh、没有文件系统、没有串口、没有引脚、没有 DMA)——它不需要那些外设,越精简越省电。而 ptab.json 里除了常规的 bootloader、dfu(升级区)、HCPU_FLASH_CODE(主固件)、BUILTIN_RESOURCE(内置资源)、fs_root(文件系统镜像,装样书),还专门划了一块 wave_table(标签 CUSTOM_EPD_WAVE_TABLE)——那是给墨水屏波形文件预留的,第 4 章会用到。
| 目录 / 文件 | 职责 |
|---|---|
Kconfig.board | 板级引脚宏:EPD 接口、触控、TPS 电源、按键 |
SConscript | 该板 .c 文件的 SCons 编译入口(v11/v12 会条件式带上 base) |
hcpu/ | 主核配置:board.conf、rtconfig.py、Kconfig.board、custom_mem_map.h |
lcpu/ | 低功耗核配置:极简 board.conf、rtconfig.py |
ptab.json | Flash 分区表:flash2 / psram1 / hpsys_ram / lpsys_ram |
sf32-oed-epd_base/*.c | 公共板级驱动:bsp_init.c、bsp_pinmux.c、bsp_power.c、bsp_lcd_tp.c |
# sf32-oed-epd_v11/hcpu/Kconfig.board:定义"这块板"的唯一宏,再引 base
config BSP_USING_BOARD_SF32_OED_EPD_V11
bool
select SOC_SF32LB52X
select BF0_HCPU
default y
rsource "../../sf32-oed-epd_base/Kconfig.board"
# sf32-oed-epd_v11/lcpu/board.conf(低功耗核:外设几乎全关)
# CONFIG_RT_USING_FINSH is not set
# CONFIG_RT_USING_DFS is not set
# CONFIG_RT_USING_SERIAL is not set
# CONFIG_RT_USING_PIN is not set
# CONFIG_BSP_USING_MPI is not set
# CONFIG_BSP_USING_DMA is not set
# sf32-oed-epd_v11/lcpu/rtconfig.py
CORE = "LCPU"
CHIP = 'SF32LB52X'
TARGET_NAME = 'lcpu'
// sf32-oed-epd_v11/ptab.json(节选:文件系统区与波形表区)
{
"offset": "0x00E00000",
"max_size": "0x00C0000",
"img": "fs_root",
"tags": ["FS_REGION"]
},
{
"offset": "0x00EC0000",
"max_size": "0x00020000",
"img": "wave_table",
"tags": ["CUSTOM_EPD_WAVE_TABLE"]
}
读板级配置,抓住"一主一从一表"三个抓手:hcpu 是主(应用真正用的外设都在 hcpu/board.conf 里开),lcpu 是从(低功耗协处理器,能不开就不开),ptab.json 是地图(固件烧哪、样书烧哪、波形烧哪)。尤其留意 board.conf 里的 CONFIG_BSP_USING_EPIC=y(EPIC 是墨水屏图像加速引擎,第 4 章的主角之一)与 CONFIG_BSP_USING_LCD=y——它们决定了屏驱代码是否被编进来。
别看 lcpu/board.conf 全是"# ... is not set",就以为它不重要。LCPU 固件是要作为镜像编进主固件的(第 2 章说过 AddLCPU(...))。一旦在 lcpu 里误开了带中断的外设,反而可能干扰低功耗唤醒。改低功耗行为时,第一优先级是去查 lcpu/,而不是 HCPU。
先把 Kconfig 说人话:它是一种文本化的"配置菜单"格式(源自 Linux 内核生态,RT-Thread 也用它),用 config / choice / select 等关键字描述"有哪些开关、默认开不开、选了一个会不会连带开别的",再由 menuconfig 把菜单渲染出来。本项目里 project/Kconfig.proj 放项目级配置,各板的 Kconfig.board 放板级引脚——构建时先按 --board 找到板配置,再和项目配置合起来生成最终的编译开关(CONFIG_* 宏)。这就是第 2 章那句"配置放哪比背命令重要"的落点。
整份 Kconfig.proj 里,和屏幕直接相关的核心是一段 choice "Custom LCD driver"——一个只能选一项的选项组,让用户在 四块屏里挑一块。前三块是 8-bit 并行大屏(YZC085 V1.1 版、YZC085 V1.2 版、R7D005 1448×1072),第四块 LCD_USING_EPD_OPMO37A3 是 SPI 小屏(240×416)。每个选项用 select 自动连带打开它依赖的宏:8-bit 屏 select BSP_LCDC_USING_EPD_8BIT(这条总线宏正好是 display_dbi 驱动被编进来的条件,见 3.4),SPI 屏 select LCD_USING_EPD_SPI;触控芯片也跟着换——V1.1 配 TSC_USING_FT5446U_V01,V1.2 配 TSC_USING_GT967,SPI 小屏配 TSC_USING_FT6336U。
选完屏,还要告诉系统"这屏多大、多密"——三个宏 LCD_HOR_RES_MAX(横向像素)、LCD_VER_RES_MAX(纵向像素)、LCD_DPI(每英寸点数)按屏幕宏分别给默认值。这两个分辨率宏不是摆设:帧缓冲、屏驱的"每行时钟数"(LCD_HOR_RES_MAX/4)、行数(LCD_VER_RES_MAX)全由它们驱动,第 4 章会直接看到它们出现在时序配置里。
| 屏幕宏 | 分辨率 | DPI | 总线宏 | 触控芯片 |
|---|---|---|---|---|
LCD_USING_EPD_YZC085_V100 | 1032×758 | 300 | BSP_LCDC_USING_EPD_8BIT | FT5446U_V01 |
LCD_USING_EPD_YZC085_V100_V12 | 1032×758 | 300 | BSP_LCDC_USING_EPD_8BIT | GT967 |
LCD_USING_EPD_R7D005 | 1448×1072 | 315 | BSP_LCDC_USING_EPD_8BIT | FT5446U_V01 |
LCD_USING_EPD_OPMO37A3 | 240×416 | 130 | LCD_USING_EPD_SPI | FT6336U |
# project/Kconfig.proj · choice "Custom LCD driver"(节选)
choice
prompt "Custom LCD driver"
default LCD_USING_EPD_YZC085_V100 if BSP_USING_BOARD_SF32_OED_EPD_V11
default LCD_USING_EPD_YZC085_V100_V12 if BSP_USING_BOARD_SF32_OED_EPD_V12
config LCD_USING_EPD_YZC085_V100
bool "6.0 rect electronic paper display(EPD YZC085_V1.05 1032x758) for V1.1 board"
select TSC_USING_FT5446U_V01 if BSP_USING_TOUCHD
select LCD_USING_OPM060D
select BSP_LCDC_USING_EPD_8BIT
endchoice
# project/Kconfig.proj · 分辨率与 DPI 宏(按屏给出默认值)
config LCD_HOR_RES_MAX
int
default 1448 if LCD_USING_EPD_R7D005
default 1032 if LCD_USING_EPD_YZC085_V100
default 1032 if LCD_USING_EPD_YZC085_V100_V12
default 240 if LCD_USING_EPD_OPMO37A3
config LCD_VER_RES_MAX
int
default 1072 if LCD_USING_EPD_R7D005
default 758 if LCD_USING_EPD_YZC085_V100
default 758 if LCD_USING_EPD_YZC085_V100_V12
default 416 if LCD_USING_EPD_OPMO37A3
config LCD_DPI
int
default 315 if LCD_USING_EPD_R7D005
default 300 if LCD_USING_EPD_YZC085_V100
default 130 if LCD_USING_EPD_OPMO37A3
想看这四块屏是怎么"长"出来的?在 epdiy-epub/project 下跑一次 scons --board=sf32-oed-epd_v11 --board_search_path=.. --menuconfig,进入 Custom LCD driver 子菜单就能看到它们,且默认项会自动选成 YZC085_V100——因为 default ... if BSP_USING_BOARD_SF32_OED_EPD_V11。改 --board,默认项跟着变。这就是"板宏决定默认屏"的直观演示。
第 2 章已经见过编译命令,这里把"选板"的机制补完整:scons --board=sf32-oed-epd_v11 --board_search_path=.. -j8 里的 --board 是板配置目录名,--board_search_path=.. 告诉 SCons 去哪找它(.. 指 epdiy-epub/ 根目录,四套配置都在那)。编译产物落在 project/build_sf32-oed-epd_v11_hcpu/ 这类 build_<板名>_hcpu 目录里,烧录时用其中自动生成的 uart_download.bat,按提示输入串口号即可。想换板,把 --board 换成 sf32-oed-epd_v12 或 sf32-oed-epd_v12_spi 就行——代码不用改一行,改的是"选哪块板"。
这套"换板不改代码"是怎么挂钩的?链路分三步。第一步,hcpu/Kconfig.board 定义唯一板宏(BSP_USING_BOARD_SF32_OED_EPD_V11 / V12 / V12_SPI)。第二步,各板 SConscript 用 depend = ['BSP_USING_BOARD_...'] 声明"只有这个板宏为真时才编译我",v11/v12 还条件式地把 base 的 SConscript 追加进来(所以 base 只编进 v11/v12,不编进 v12_spi)。第三步,屏幕驱动目录 display_dbi / display_spi 各自用 depend 挂到 3.3 的总线宏上——选 8-bit 屏就编 DBI 驱动,选 SPI 屏就编 SPI 驱动。三张表串起来:板宏 → 默认屏宏 → 总线宏 → 驱动目录。
| --board 值 | 板宏 | 默认屏宏 | 编入的驱动目录 |
|---|---|---|---|
sf32-oed-epd_v11 | BSP_USING_BOARD_SF32_OED_EPD_V11 | LCD_USING_EPD_YZC085_V100 | display_dbi |
sf32-oed-epd_v12 | BSP_USING_BOARD_SF32_OED_EPD_V12 | LCD_USING_EPD_YZC085_V100_V12 | display_dbi |
sf32-oed-epd_v12_spi | BSP_USING_BOARD_SF32_OED_EPD_V12_SPI | LCD_USING_EPD_OPMO37A3 | display_spi |
# epdiy-epub/project 下执行(README · 「编译与烧录」)
scons --board=sf32-oed-epd_v11 --board_search_path=.. -j8
# 编译完成后下载(批处理脚本,按提示输入串口号)
build_sf32-oed-epd_v11_hcpu\uart_download.bat
# README · 「menuconfig」
scons --board=sf32-oed-epd_v11 --board_search_path=.. --menuconfig
# sf32-oed-epd_v11/SConscript:只有本板宏为真才编,且条件式带上 base
group = DefineGroup('Drivers', src, depend = ['BSP_USING_BOARD_SF32_OED_EPD_V11'], CPPPATH = path)
if GetDepend('BSP_USING_BOARD_SF32_OED_EPD_V11'):
group = group + SConscript(os.path.join(cwd, '../sf32-oed-epd_base/SConscript'), variant_dir="base", duplicate=0)
# src/boards/display_dbi/SConscript(8-bit 并行,依赖总线宏)
group = DefineGroup('Drivers', src, depend = ['BSP_LCDC_USING_EPD_8BIT'], CPPPATH = CPPPATH)
# src/boards/display_spi/SConscript(SPI 串行,依赖总线宏)
group = DefineGroup('Drivers', src, depend = ['LCD_USING_EPD_SPI'], CPPPATH = CPPPATH)
把 3.2-3.4 串成一句话记住:hcpu/Kconfig.board 认板(板宏)→ Kconfig.proj 认屏(屏宏 + 分辨率)→ SConscript 认口(总线宏)→ display_dbi/display_spi 干活。以后你给项目换屏、换板,脑子里先走这条链,就知道该改哪个文件、为什么其它文件不用动。
README「硬件连接」特意强调"注意排线方向"——这不是客套话。8-bit 并行屏的排线(D0-D7 + 控制信号)和 SPI 屏的排线(SDA/SCK/DC/CS/RES/BUSY)物理结构不同,插反轻则点不亮、重则烧板。选板时先看清自己手里是 V1.1/V1.2 并行屏还是 SPI 小屏,再决定用哪个 --board。
用一句话分别解释:板级配置、Kconfig、--board。要求先说自己的理解,再对照 3.1 / 3.3 / 3.4 检查有没有把"板 / 配置格式 / 选板命令"三个概念搞混。
板级配置是"某块板"的硬件差异清单;Kconfig 是描述配置菜单的文本格式;--board 是 SCons 里选板的命令行参数。
板级配置:针对某一块具体开发板的引脚、外设、内存布局等硬件差异整理成的配置集合,编译时据此生成合适固件;本项目有 v11 / v12 / v12_spi 三套,V1.1/V1.2 共用 base。Kconfig:文本化的配置菜单格式(源自 Linux/RT-Thread 生态),用 config / choice / select 描述选项树,配合 menuconfig 渲染。--board:scons 命令里指定板配置目录名的参数,配合 --board_search_path 定位板目录。
对照 3.3,把 Kconfig.proj 里四块屏的分辨率、DPI、总线宏列成一张表,并指出哪一块走 SPI、分别配哪颗触控芯片。
看 choice "Custom LCD driver" 的四个 config,以及 LCD_HOR_RES_MAX / LCD_VER_RES_MAX / LCD_DPI 的 default ... if 分支。
YZC085_V100(V1.1):1032×758、DPI 300、BSP_LCDC_USING_EPD_8BIT、FT5446U_V01;YZC085_V100_V12(V1.2):1032×758、DPI 300、8BIT、GT967;R7D005:1448×1072、DPI 315、8BIT、FT5446U_V01;OPMO37A3(SPI):240×416、DPI 130、LCD_USING_EPD_SPI、FT6336U。走 SPI 的是 OPM037A3,其余三块都是 8-bit 并行。
读 sf32-oed-epd_v11/Kconfig.board 与 sf32-oed-epd_v12_spi/Kconfig.board,指出两者的 EPD 信号引脚各是什么、分别对应哪种接口;再解释为什么 v12_spi 必须自带一套 bsp_*.c。
v11 的 base 里是 EPD_D0-D7 / EPD_LE / EPD_OE / EPD_SPH / EPD_STV / EPD_CPV / EPD_GMODE;v12_spi 里是 EPD_SDA / EPD_SCK / EPD_DC / EPD_RES / EPD_CS / EPD_BUSY。
v11(经 base)定义的是 8-bit 并行接口信号:8 根数据线 D0-D7 加 CLK、LE(锁存)、OE(输出使能)、SPH、STV、CPV、GMODE 等控制线;v12_spi 定义的是 SPI 串行接口信号:SDA(数据)、SCK(时钟)、DC(数据/命令)、RES(复位)、CS(片选)、BUSY(忙)。两者除了按键与电源引脚重合,屏接口引脚完全不同,bsp_lcd_tp.c、bsp_pinmux.c 里的引脚复用逻辑必须重写,所以 v12_spi 不复用 base,而是自带一份——这正印证了 3.1 那句"接口变了,base 就变了"。
假设要新增一块 8-bit 并行、分辨率 1200×800、DPI 264 的墨水屏给 V1.2 板用。按 3.3-3.4 的链路,列出你需要改动的全部位置(含 Kconfig.proj 里要新增的内容),并说明为什么 display_dbi 目录本身可能不用改。
从"认屏"到"认口"想:choice 里加一个新 config 并 select BSP_LCDC_USING_EPD_8BIT;分辨率/DPI 加 default ... if;屏驱配置文件参照第 4 章模板。注意 1200×800 仍走 8-bit 并行口,总线宏与 DBI 目录不变。
(1) project/Kconfig.proj:在 choice "Custom LCD driver" 下新增一个 config LCD_USING_EPD_XXX_V12,bool "6.0 rect ... 1200x800 for V1.2 board",并 select 对应触控、select LCD_USING_OPM060D、select BSP_LCDC_USING_EPD_8BIT;(2) 同一文件给 LCD_HOR_RES_MAX 加 default 1200 if ...、LCD_VER_RES_MAX 加 default 800 if ...、LCD_DPI 加 default 264 if ...;(3) 可选:在 choice 的 default ... if BSP_USING_BOARD_SF32_OED_EPD_V12 换成本新屏宏;(4) 新建屏驱配置文件(复制 epd_configs_opm060d.c 为模板,改波形数组、时序、VCOM,见第 4 章)。因为这块新屏仍走 BSP_LCDC_USING_EPD_8BIT 总线,display_dbi/epd_display.c 的接口与 SConscript 的 depend 都不用动——这就是"认口"把驱动与屏解耦的好处。