固件库开发指南
本文档介绍芯片模板中 HAL(硬件抽象层)和 LL(底层)固件库的开发方法,以及通用模板的使用方式。
HAL vs LL 库对比
| 特性 | HAL | LL | STD |
|---|---|---|---|
| 抽象层级 | 高 | 低 | 中 |
| 代码量 | 较大 | 较小 | 中等 |
| 性能 | 较低 | 较高 | 中等 |
| 易用性 | 最易 | 较难 | 中等 |
| 可移植性 | 最好 | 一般 | 一般 |
| 适用场景 | 快速开发 | 性能敏感 | 传统项目 |
通用模板目录
芯片模板中的通用模板位于 common/ 目录下,提供跨外设共享的代码片段:
common/
├── gpio/
│ ├── gpio_enable_hal.ejs # HAL 使能 GPIO
│ ├── gpio_enable_ll.ejs # LL 使能 GPIO
│ ├── gpio_disable_hal.ejs # HAL 禁用 GPIO
│ └── peripheral_gpio_init.ejs # 外设 GPIO 初始化
├── dma/
│ ├── dma_hal.ejs # DMA HAL 初始化结构
│ ├── dma_struct.ejs # DMA 通道配置
│ └── dma_unit.ejs # 单个 DMA 单元
├── nvic/
│ ├── nvic_enable_hal.ejs # NVIC 使能中断
│ └── nvic_disable_hal.ejs # NVIC 禁用中断
├── hal_conf.ejs # HAL 库配置文件
├── hal_msp.ejs # HAL MSP 初始化
├── main.c.ejs # 主函数模板
├── main.h.ejs # 主头文件模板
└── define.cjs # 通用宏定义
GPIO 通用模板
gpio_enable_hal.ejs
用于生成外设 GPIO 的 HAL 库使能代码:
// 模板输出示例
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) {
// 使能 GPIO 时钟
__HAL_RCC_GPIOx_CLK_ENABLE();
// 配置引脚
// ...
}
gpio_enable_ll.ejs
用于生成外设 GPIO 的 LL 库使能代码:
// 模板输出示例
void LL_GPIO_Init(GPIO_TypeDef *GPIOx, LL_GPIO_InitTypeDef *GPIO_Init) {
// LL 库更加精简
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOx);
// 直接寄存器操作
// ...
}
gpio_disable_hal.ejs
用于生成外设 GPIO 的 HAL 库禁用代码:
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin) {
// 复位引脚配置
// 可选:禁用 GPIO 时钟
}
peripheral_gpio_init.ejs
通用的外设 GPIO 初始化代码,被各外设模板引用:
// 用于生成外设特定引脚的 GPIO 初始化
void Peripheral_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// USART1 TX: PA9
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// USART1 RX: PA10
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
DMA 通用模板
dma_hal.ejs
DMA HAL 初始化结构模板:
DMA_HandleTypeDef hdma_usart1_tx;
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_usart1_tx);
dma_struct.ejs
DMA 通道配置数据结构:
// 模板处理的变量结构
{
channel: 'DMA1_Channel4',
direction: 'MEMORY_TO_PERIPH',
peripheral: 'USART1',
stream: 'TX',
priority: 'LOW',
mode: 'NORMAL'
}
dma_unit.ejs
单个 DMA 传输单元模板,处理基本的 DMA 传输参数。
NVIC 通用模板
nvic_enable_hal.ejs
// NVIC 中断使能配置
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
nvic_disable_hal.ejs
// NVIC 中断禁用
HAL_NVIC_DisableIRQ(USART1_IRQn);
EJS 模板语法参考
芯片模板使用 EJS(Embedded JavaScript)作为模板引擎。以下是常用语法:
变量输出
<!-- 输出变量值 -->
<%= variableName %>
<!-- 输出并转义 HTML -->
<%- htmlContent %>
条件判断
<% if (condition) { %>
条件成立的内容
<% } else { %>
条件不成立的内容
<% } %>
循环
<% items.forEach(function(item) { %>
<%- item.name %>
<% }) %>
模板之间的交互流程
用户配置界面
↓
pre.cjs (数据预处理)
↓ 处理后的数据
EJS 模板 (代码渲染)
↓ 生成的代码
最终工程文件 (.c/.h)
pre.cjs 示例
// GPIO pre.cjs
module.exports = ({ ip, ide, locals }) => {
const global = locals.global
// 获取 GPIO 数据
let ip_pins = global.GPIO.get_ip_gpios(
[ip.$name, 'EVENTOUT', 'IR', 'MCO']
)
// 固件参数映射
global.GPIO.set_gpio_define_map(
locals.defineMap,
ip.firmware.value,
ip_pins.pins
)
// 合并数据
Object.assign(ip, ip_pins)
// 生成中断处理
set_nvic_handlers(global, ip)
}
最佳实践
- 模板复用:通用功能(GPIO初始化、DMA配置、NVIC配置)应抽取到
common/目录 - 固件分离:HAL 和 LL 模板分开维护,通过
firmware参数选择 - 版本管理:模板的每次修改应递增版本号,确保已有芯片配置不受影响
- 注释清晰:生成的代码应包含有意义的注释,说明配置意图
- 命名规范:模板文件名应清晰表达其用途(如
gpio_enable_hal.ejs)
