外设配置详解
本文档详细介绍外设配置的技术细节,是 外设配置教程 的深入补充。
外设节点属性
每个外设节点的配置分散在以下文件中:
info.cjs — 定义外设实例
// chip/GD32F103XX/GD32F103ZET6/info.cjs
ips: [
{ name: 'ADC', max: 3 }, // 节点名称和最大实例数
{ name: 'GPIO', max: 1 },
{ name: 'TIM', max: 8 },
{ name: 'USART', max: 5 }
]
code.cjs — 指定模板版本
// chip/GD32F103XX/code.cjs
GPIO: {
ip_type: 'GPIO',
version: '1.0'
}
ips/{IP}/ui/*.cjs — 项目配置和功能函数
// ips/GPIO/ui/gpio_base.cjs 中的 project 对象
project: {
generate_code: { value: true, fixed: false },
rank: { value: 2, fixed: true },
function_name: { value: 'Studio_GPIO_Init' },
firmware: {
value: 'std',
options: [
{ label: 'Standard', value: 'std' }
]
},
file_name: 'gpio',
ip_type: 'GPIO'
}
| 属性 | 说明 |
|---|---|
generate_code | 是否生成该外设的初始化代码 |
rank | 外设在代码中的初始化顺序(数值越小越先初始化) |
function_name | 生成的外设初始化函数名称 |
firmware | 支持的固件类型(std/hal/ll) |
file_name | 生成的源文件名(不含扩展名) |
ip_type | 外设类型标识,对应 ips/ 目录名 |
外设使能控制
外设没有显式的 enable 属性,而是通过 functions 中的 enabled() 函数判断:
// ips/GPIO/ui/gpio_base.cjs
functions: {
enabled: () => {
// 返回 true 表示外设可用
return true
}
}
初始化脚本
初始化代码的生成逻辑在 ips/{IP}/code/{version}/pre.cjs:
// ips/GPIO/code/1.0/pre.cjs
module.exports = ({ ip, ide, locals }) => {
const global = locals.global
let ip_pins = global.GPIO.get_ip_gpios([ip.$name])
Object.assign(ip, ip_pins)
}
## MODE 节点与 signals
`ips/{IP}/ui/*.cjs` 中通过 `modes` 和 `signals` 两个节点控制外设的使能状态和引脚自动分配。
### modes — 外设工作模式
定义外设的 Mode 参数,每个选项通过 `signals` 数组声明该模式所需的引脚功能。`disable` 选项表示外设禁用。
**USART 示例:**
```javascript
modes: {
Mode: {
label: 'Mode',
type: 'select',
value: 'disable', // 默认禁用
options: [
{ label: 'Disable', value: 'disable' },
{ label: 'Asynchronous', value: 'asynchronous',
signals: ['TX', 'RX'] }, // 需要 TX、RX 引脚
{ label: 'Synchronous', value: 'synchronous',
signals: ['TX', 'RX', 'CK'] }, // 额外需要 CK 引脚
{ label: 'Single Wire', value: 'single_wire',
signals: ['TX'] }, // 仅需 TX 引脚
{ label: 'RTS/CTS', value: 'rts_cts',
signals: ['RTS', 'CTS'] } // 硬件流控引脚
]
}
}
SPI 示例:
modes: {
Mode: {
value: 'disable',
options: [
{ label: 'Disable', value: 'disable' },
{ label: 'Full-Duplex Master', value: 'master_2lines',
signals: ['MOSI', 'MISO', 'SCK'] },
{ label: 'Half-Duplex Master', value: 'master_1line',
signals: ['MOSI', 'SCK'] }, // 半双工只需 MOSI
{ label: 'Receive Only Master', value: 'master_2lines_rx_only',
signals: ['MISO', 'SCK'] } // 仅接收只需 MISO
]
},
NSSMode: {
options: [
{ label: 'Software', value: 'soft' },
{ label: 'Hard Input', value: 'hard_input',
signals: ['NSS'] }, // 硬件 NSS 需要 NSS 引脚
{ label: 'Hard Output', value: 'hard_output',
signals: ['NSS'],
disabled: "@get:$root.Mode.value.includes('slave')" }
]
}
}
ADC 示例(checkbox 模式):
modes: {
IN0: {
label: 'IN0',
type: 'checkbox',
value: false,
signals: ['IN0'] // 勾选时自动分配 IN0 引脚
},
IN1: { label: 'IN1', type: 'checkbox', value: false, signals: ['IN1'] }
}
signals — 引脚功能定义
顶层 signals 定义每个引脚信号的默认配置:
signals: {
TX: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
RX: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
CK: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
RTS: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
CTS: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' },
NSS: { mode: 'AF_PP', pull: 'NOPULL', speed: 'High', level: 'NA' }
}
当用户选择某个 Mode 后,系统根据 signals 数组自动查找并分配对应的引脚。取消选择或切换模式时,自动释放不再需要的引脚。
enabled() — 使能判断
functions.enabled() 根据 Mode 值判断外设是否使能:
enabled: () => {
return $root.Mode.value !== 'disable'
}
当 Mode === 'disable' 时,外设不可用,相关的 NVIC、DMA 等面板也会随之隐藏。
面板类型
Parameter 面板
用于配置外设的基本参数。通常包含下拉选择、输入框、复选框等元素:
Parameter: {
label: 'Parameter',
type: 'table', // 可配置为 table 类型
head: { /* 表头定义 */ },
body: { /* 表体数据 */ }
}
NVIC 面板
NVIC 面板由两个文件协同定义:
1. nvic.cjs — 中断定义(chip/{系列}/{型号}/nvic.cjs)
定义芯片支持的所有中断及其与外设的关联:
module.exports = {
USART0_IRQn: {
label: 'USART0 global interrupt',
ip_names: ['USART0'] // 关联的外设实例名
},
USART1_IRQn: {
label: 'USART1 global interrupt',
ip_names: ['USART1']
},
TIMER0_BRK_IRQn: {
label: 'TIMER0 break interrupt',
ip_names: ['TIM0']
},
TIMER0_UP_IRQn: {
label: 'TIMER0 update interrupt',
ip_names: ['TIM0'] // 一个 IP 可以有多个中断
},
ADC0_1_IRQn: {
label: 'ADC0 and ADC1 interrupt',
ip_names: ['ADC0', 'ADC1'] // 多个 IP 共享一个中断
},
EXTI0_IRQn: {
label: 'EXTI line 0 interrupt',
ip_names: ['GPIO'] // GPIO 的 EXTI 中断
}
}
| 字段 | 说明 |
|---|---|
label | 中断在 UI 中显示的名称 |
ip_names | 关联的外设实例名数组,只有 info.cjs 中存在该实例时,此中断才会生效 |
2. nvic_base.cjs — NVIC 面板逻辑(ips/NVIC/ui/nvic_base.cjs)
定义 NVIC 面板的生成逻辑和中断过滤:
// NVIC 面板结构
NVIC: {
label: 'NVIC Settings',
type: 'table',
head: {
irqn: { label: 'IRQn', type: 'text' },
enable: { label: 'Enable', type: 'checkbox' },
priority: { label: 'Preemption Priority', type: 'select', options: [...] },
subpriority:{ label: 'Sub Priority', type: 'select', options: [...] }
},
body: { /* 由 get_nvic_table_body() 动态生成 */ }
}
关键函数:
functions: {
// 为指定外设实例筛选关联的中断
get_ip_nvic_rows: (instance) => {
// 遍历 nvic.cjs,找出 ip_names 包含该实例的中断
// 返回匹配的中断名数组
},
// 生成外设的 NVIC 面板 UI
get_ip_nvic_ui_by_instance: (instance) => {
const rows = get_ip_nvic_rows(instance)
// 自动从 NVIC 主面板复制对应的中断行
// 如果外设有 DMA,自动关联 DMA 通道中断
},
// 收集所有已启用且不包含 DMA 的中断
get_enabled_nvics: (_global, instance) => {
// 遍历所有外设实例的 NVIC.body
// 返回已启用的中断列表(含优先级、是否为多实例共享)
}
}
DMA 中断自动关联:
当外设配置了 DMA 通道时,NVIC 面板自动包含对应的 DMA 中断:
// DMA 通道 → DMA 中断映射
DMA1_Channel1 → DMA1_Channel1_IRQn
DMA1_Channel2 → DMA1_Channel2_IRQn (或 DMA1_Channel2_3_IRQn)
DMA1_Channel3/4 → DMA1_Channel3_IRQn (或 DMA1_Channel3_4_IRQn)
DMA 面板
DMA 面板由两个文件协同定义:
1. dma.cjs — DMA 请求映射(chip/{系列}/{型号}/dma.cjs)
定义芯片中所有外设的 DMA 请求:
module.exports = {
USART0_TX: {}, // USART0 发送 DMA 请求
USART0_RX: {}, // USART0 接收 DMA 请求
ADC0: {}, // ADC0 DMA 请求
SPI0_TX: {}, // SPI0 发送 DMA 请求
SPI0_RX: {}, // SPI0 接收 DMA 请求
TIM0_CH0: {}, // TIM0 通道0 DMA 请求
TIM0_UP: {} // TIM0 更新 DMA 请求
}
每个键为 {外设实例名}_{通道/方向} 格式。外设的 DMA 面板根据此映射筛选可用的 DMA 请求。
2. dma_base.cjs — DMA 面板逻辑(ips/DMA/ui/dma_*_base.cjs)
定义 DMA 的参数和面板生成:
// DMA 面板参数
parameters: {
channel_dma: { // DMA 通道选择
label: 'Channel',
type: 'select',
options: '@get:$root.dma_channels'
},
direction: { // 传输方向
label: 'Direction',
type: 'select',
value: 'm2m',
options: [
{ label: 'Memory To Memory', value: 'm2m' },
{ label: 'Memory To Peripheral', value: 'm2p' },
{ label: 'Peripheral To Memory', value: 'p2m' }
]
},
priority: { // DMA 优先级
label: 'Priority',
type: 'select',
value: 'Low',
options: [
{ label: 'Low', value: 'Low' },
{ label: 'Medium', value: 'Medium' },
{ label: 'High', value: 'High' },
{ label: 'Very High', value: 'VeryHigh' }
]
},
periph_inc: { // 外设地址自增
label: 'PeriphInc',
type: 'select',
value: 'Disable',
options: [
{ label: 'Disable', value: 'Disable' },
{ label: 'Enable', value: 'Enable' }
]
},
memory_inc: { // 内存地址自增
label: 'MemoryInc',
type: 'select',
value: 'Disable',
options: [
{ label: 'Disable', value: 'Disable' },
{ label: 'Enable', value: 'Enable' }
]
},
periph_data_alignment: { // 外设数据宽度
label: 'PeriphDataAlignment',
type: 'select',
value: 'Byte',
options: [
{ label: 'Byte', value: 'Byte' },
{ label: 'Half Word', value: 'HalfWord' },
{ label: 'Word', value: 'Word' }
]
},
mem_data_alignment: { // 内存数据宽度
label: 'MemDataAlignment',
type: 'select',
value: 'Byte',
options: [
{ label: 'Byte', value: 'Byte' },
{ label: 'Half Word', value: 'HalfWord' },
{ label: 'Word', value: 'Word' }
]
},
mode: { // DMA 模式
label: 'Mode',
type: 'select',
value: 'Normal',
options: [
{ label: 'Normal', value: 'Normal' },
{ label: 'Circular', value: 'Circular' }
]
}
}
关键函数:
functions: {
// 检查外设是否有 DMA 能力(根据 dma.cjs 映射)
has_ip_dma: (instance) => {
// 检查 dma.cjs 中是否有该实例对应的 DMA 请求
},
// 为外设生成 DMA 面板 UI(自动包含该外设的 DMA 请求行)
get_ip_dma_ui: (instance) => {
if (!has_ip_dma(instance)) return ''
// 返回该外设的 DMA 表格,包含 channel/direction/priority 等列
},
// 获取已启用的 DMA 配置(用于代码生成)
getDMA: (ip) => {
// 筛选 channel.value 不为空且不为 'Disable' 的行
},
// 获取 DMA 配置并映射到固件宏(含固件参数适配)
get_ip_dmas: (ip, defineMap) => {
// 额外将 channel/priority/direction/mode 等通过 defineMap 映射为 SDK 宏
},
// MemToMem 专用 DMA 获取
get_mem_dmas: () => {
// 获取 MemToMem 面板中 channel 不为 Disable 的行
}
}
GPIO 面板
GPIO 面板由 GPI〇 UI 配置文件(ips/GPIO/ui/*.cjs)定义,不依赖独立的芯片级配置文件。
GPIO 表结构:
// GPIO 面板以表格形式呈现
GPIO: {
label: 'GPIO',
type: 'table',
head: {
name: { label: 'Pin', type: 'text' },
mode: { label: 'Mode', type: 'select', options: [...] },
speed: { label: 'Speed', type: 'select', options: [...] },
level: { label: 'Level', type: 'select', options: [...] },
alternate: { label: 'Alternate', type: 'text' }
},
body: { /* 引脚数据由脚本动态填充 */ }
}
关键函数:
functions: {
// 手动选择引脚复用功能
headPeripheralPinAf: (pin, af) => {
// af.name === 'Reset_State' → 删除引脚
// 唯一复用功能 → 删除旧绑定,更新新绑定
},
// 自动设置/释放引脚(系统调用,函数名不可修改)
auto_set_pins: (target, name) => {
// target=true → addPinByAlternate 自动查找空闲引脚
// target=false → 删除未锁定的引脚
},
// 根据复用功能添加引脚(自动选择空闲引脚)
addPinByAlternate: (alternate) => {
// 查找支持该功能的空闲引脚
// 过滤系统引脚(NRST、BOOT0、SWDIO、SWCLK)
// 按 port 和 num 排序
},
// 获取 GPIO 初始化数据(核心函数)
get_ip_gpios: (gpioTypes) => {
// 1. 过滤符合类型的引脚
// 2. 生成引脚描述(用于注释)
// 3. 按 PORT 分组
// 4. 按 mode/speed 合并同配置引脚
// 5. 检查 EXTI 中断
// 6. 生成 resetPins / setPins
},
// 固件参数映射
set_gpio_define_map: (defineMap, firmware, pins) => {
// 将 UI 参数值映射为特定固件的宏定义
},
// 引脚查找
getPinByName: (name) => {},
getPinByAlternate: (alternate) => {}, // 支持 ${num} 匹配
isFreeByAlternate: (alternate) => {} // 检查是否有空闲引脚
}
## 配置最佳实践
1. **pre.cjs 是代码生成入口**:每个外设必须在 `ips/{IP}/code/{version}/pre.cjs` 中导出入口函数,用于处理配置数据并生成初始化代码
2. **enabled() 控制使能状态**:通过 `functions.enabled()` 返回值控制外设在 UI 中的可用状态,无需单独的 enable 属性
3. **面板命名规范**:使用英文 ID(如 `Parameter`、`NVIC`),中文标签(如"参数配置"、"中断配置")
4. **元素 ID 命名**:使用有意义的驼峰命名(如 `BaudRate`、`WordLength`)
5. **代码注释**:在生成代码中包含注释,提高可读性
6. **错误处理**:使用 `ShowNotification` 提示配置错误
