框架与规则
本章定义芯片模板框架的核心架构、数据流和通用规则,是理解整个系统运作方式的基础。
系统架构
芯片模板框架将一个 MCU 芯片的完整开发生态转化为一组可复用、可分发的配置文件。数据流向如下:
芯片定义层(chip/)
│
├── info.cjs → 芯片元数据和 IP 列表
├── pins.cjs → 引脚复用映射
├── nvic.cjs → 中断与 IP 关联
├── dma.cjs → DMA 请求映射
├── code.cjs → IP 版本选择
└── ide.cjs → IDE 平台配置
│
↓
IP 模板层(ips/)
│
├── ui/*.cjs → 配置界面定义(project/parameters/modes/signals/functions)
└── code/{version}/ → 代码生成(pre.cjs + *.ejs)
│
↓
通用层(common/)
│
├── define.cjs → 全局寄存器宏映射
├── gpio/ → GPIO 通用模板
├── dma/ → DMA 通用模板
└── nvic/ → NVIC 通用模板
│
↓
构建层
│
└── 合并所有配置 → 渲染 EJS 模板 → 输出工程代码
配置优先级
当多个层级定义了相同配置时,按以下优先级覆盖:
芯片型号(chip/{系列}/{型号}/) ← 最高
↓ 覆盖
系列(chip/{系列}/) ← 中
↓ 覆盖
通用(common/) ← 最低(默认值)
| 场景 | 示例 |
|---|---|
| 芯片 define 覆盖通用 define | chip/AT32F403/define.cjs 覆盖 common/define.cjs 中的同名条目 |
| 型号 ui.cjs 覆盖系列配置 | chip/GD32F103XX/GD32F103ZET6/ui.cjs 可覆盖系列级的外设设置 |
| 系列 code.cjs 指定版本 | chip/GD32F103XX/code.cjs 中 ADC: { version: '1.0' } 决定使用哪个 code 模板 |
文件约定
导出格式
所有 .cjs 配置文件必须使用 CommonJS 导出:
// ✅ 正确
module.exports = { ... }
// ✅ 正确(exports 别名)
exports.ADC = { ... }
// ❌ 错误:不支持 ES Module
export default { ... }
文件角色
| 文件 | 角色 | 必须? | 定义什么 |
|---|---|---|---|
chip/{系列}/{型号}/info.cjs | 芯片元数据 | ✅ | Flash、RAM、IP 列表 |
chip/{系列}/{型号}/pins.cjs | 引脚映射 | ✅ | 引脚复用功能 |
chip/{系列}/{型号}/nvic.cjs | 中断定义 | ✅ | 中断与 IP 的关联 |
chip/{系列}/code.cjs | 版本映射 | ✅ | IP → 模板版本 |
chip/{系列}/ide.cjs | IDE 配置 | ✅ | 支持的 IDE 和平台 |
chip/{系列}/define.cjs | 寄存器宏 | - | 覆盖 common 的系列特有宏 |
ips/{IP}/ui/*.cjs | UI 配置 | ✅ | project/parameters/modes/signals/functions |
ips/{IP}/code/{version}/pre.cjs | 代码入口 | ✅ | 数据处理逻辑 |
ips/{IP}/code/{version}/*.ejs | 代码模板 | ✅ | 按固件类型输出代码 |
common/define.cjs | 全局宏映射 | ✅ | ADC/DMA/I2C/SPI 等参数到 SDK 宏 |
UI 配置规则
ips/{IP}/ui/*.cjs 文件必须导出以下五个节点之一或多个。每个节点的存在决定了系统如何处理该外设的 UI 和行为。
导出节点一览
| 节点 | 类型 | 说明 |
|---|---|---|
project | Object | 外设项目元配置 |
parameters | Object | 用户可配置的参数定义 |
modes | Object | 外设工作模式(含 signals 映射) |
signals | Object | 引脚信号的默认电气配置 |
functions | Object | 功能函数(生命周期、使能判断、数据处理) |
project — 必须导出
project: {
generate_code: { value: true }, // 是否生成代码
rank: { value: 2 }, // 初始化顺序,数字越小越先
function_name: { value: 'xxx' }, // 生成的函数名,支持 @get: 动态计算
firmware: { // 支持的固件
value: 'std',
options: [{ label: 'Standard', value: 'std' }]
},
file_name: 'xxx', // 生成的文件名(不含扩展名)
ip_type: 'XXX' // 对应 ips/ 目录名
}
parameters — 用户配置参数
定义了外设在 UI 中可配置的所有参数。每个参数包含 label、type、value,可选 visible、disabled、options(select 类型)等。
modes — 工作模式与引脚关联
Mode 参数必须定义在 modes 节点中(而非 parameters),因为 Mode 选项中的 signals 数组会触发系统的自动引脚管理。
规则:
| 规则 | 说明 |
|---|---|
Mode disable 值 | 每个外设的 Mode 必须包含 value: 'disable' 选项,表示禁用状态 |
signals 数组 | 每个非 disable 选项声明该模式需要的信号名 |
| 信号名一致性 | signals 数组中的名称必须与顶层 signals 节点的键一致 |
| 自动引脚分配 | 选择 Mode 时,系统根据 signals 数组自动查找并绑定对应引脚 |
modes: {
Mode: {
value: 'disable',
options: [
{ label: 'Disable', value: 'disable' },
{ label: 'Asynchronous', value: 'async', signals: ['TX', 'RX'] }
]
}
}
signals — 引脚信号默认配置
顶层 signals 必须定义每个信号名的默认电气配置:
signals: {
TX: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
RX: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' }
}
functions — 功能函数
| 函数 | 签名 | 说明 |
|---|---|---|
enabled | () => boolean | 外设是否使能。通常判断 $root.Mode.value !== 'disable' |
on__mounted | () => void | 外设加载完成后的初始化回调 |
on__change | (params) => void | 任何配置项变化时触发 |
on__enableChange | () => void | 外设使能状态变化时触发 |
响应式系统
框架基于 @get / @set / @value 实现响应式数据绑定。
@get — 响应式读取
建立依赖追踪,当引用的值变化时自动重新计算:
// 引用当前外设参数
disabled: "@get: $root.NbrOfConversion.value <= 1"
// 引用时钟树节点
inclk: "@get: $clock['HXTAL'].outclk"
// 引用其他外设
visible: "@get: ADC0.enabled() && ADC1.enabled()"
// 计算表达式
outclk: "@get: $parent.inclk * parseInt($parent.selected.label.replace('x', ''))"
@set — 响应式写入
将计算结果写回指定路径:
// 双向绑定
value: "@set: $parent.inclk"
// 条件写入
semaphore: "@set: '@get: $parent.value === \"Disable\" ? \"\" : $parent.value'"
@value — 一次性取值
取值但不建立依赖追踪:
value: "@value: $pins.pins.length"
生命周期
外设从加载到代码生成的完整时序:
1. on__mounted ← 外设加载完成,初始化数据(如构建 pinsMap)
↓
2. 用户配置交互 ← Mode 切换、参数修改、引脚选择
↓
3. on__change ← 每次配置变化触发(含 id/value/path)
↓
4. on__enableChange ← 使能状态变化时触发
↓
5. 构建触发 ← 用户点击生成/构建
↓
6. pre.cjs 执行 ← 数据处理:get_ip_gpios / get_ip_dmas 等
↓
7. EJS 模板渲染 ← 根据 firmware 选择对应 .ejs 文件渲染
↓
8. 输出工程文件 ← .c / .h 文件写入工程目录
代码生成管线
从用户配置到输出代码的完整链路:
用户 UI 配置数据 (ip 对象)
│
↓ pre.cjs 入口
数据预处理(GPIO/DMA/NVIC 函数调用)
│
├── GPIO.get_ip_gpios() → 引脚数据分组合并
├── GPIO.set_gpio_define_map() → 参数映射为固件宏
├── DMA.get_ip_dmas() → DMA 配置 + 固件映射
├── NVIC.get_enabled_nvics() → 中断列表 + 优先级
└── RCU.get_ip_clock() → 外设时钟频率
│
↓ ip 对象扩展完成
EJS 模板渲染(std.c.ejs / hal.c.ejs / ll.c.ejs)
│
↓ 模板引擎替换变量
最终 C/H 代码输出
pre.cjs 约定
- 入口函数签名:
module.exports = ({ ip, ide, locals }) => {...} ip:当前外设的完整配置数据locals.global:所有外设的函数集合locals.defineMap:合并后的 define 映射表- 通过
Object.assign(ip, ...)将处理结果合并回 ip 对象
