RT-Thread HC32 系列 BSP 制作教程:从模板复制到工程生成的完整实战指南
【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread
本文以 RT-Thread 开源仓库中 XHSC(小华半导体)HC32 系列的 BSP 框架为对象,系统讲解如何基于通用模板制作一个新的开发板 BSP。文章以官方评估板ev_hc32f4a0_lqfp176的制作全过程为例,覆盖 BSP 框架结构、board 配置、Kconfig 修改、链接脚本与构建脚本调整、Env 工程重生成,以及 BSP 制作与提交规范,帮助开发者掌握为任意 HC32 芯片/开发板新增 BSP 的完整方法与最佳实践。
1. 背景:为什么要重构 HC32 系列 BSP
为了让开发者更方便地使用 BSP 进行开发,XHSC 开发团队重新整理了 RT-Thread 仓库中现有的 HC32 系列 BSP,推出了新的 BSP 框架。新框架在易用性、移植便利性、驱动完整性、代码规范性等方面都有较大提升,其核心思想是将公共代码与特定开发板代码分离:
- 在
bsp/hc32下的libraries 文件夹中,统一存放可以被多个 BSP 引用的固件库(DDL 库)以及基于 DDL 库的多系列通用外设驱动文件; - 每个特定开发板 BSP只通过引用方式包含自己需要的库文件或驱动文件,而不重复维护副本。
这种设计大大提高了代码复用率、降低了 BSP 的维护成本,同时可以为开发者提供更丰富的驱动文件,使其更容易找到所需资源。新 BSP 框架的主要特性包括:
- 提供多系列 BSP 模板,大大降低新 BSP 的添加难度;
- 每个 BSP 都配有齐全的驱动文件,开发者可以方便地使用所有驱动。
以 F4 系列为例,通用库与特定开发板 BSP 的分工如下表所示:
| 项目 | 文件夹 | 说明 |
|---|---|---|
| 通用库 | bsp/hc32/libraries | 存放 DDL 库以及基于 DDL 库的多系列通用外设驱动文件,其驱动源码位于bsp/hc32/libraries/hc32_drivers下 |
| 特定开发板 BSP | bsp/hc32/ev_hc32f4a0_lqfp176 | 在 BSP 模板的基础上修改而成,仅包含与这块开发板强相关的配置与链接文件 |
仓库中bsp/hc32目录下现有的官方与第三方 BSP 包括ev_hc32f334_lqfp64(F334)、ev_hc32f448_lqfp80、ev_hc32f460_lqfp100_v2、ev_hc32f467_lqfp144、ev_hc32f472_lqfp100、ev_hc32f4a0_lqfp176、ev_hc32f4a2_lqfp176、ev_hc32f4a8_lqfp176以及立创开发板的lckfb-hc32f4a0-lqfp100,完整支持情况可参见 HC32 BSP 说明。
2. 制作前的知识准备
制作一个 BSP 的过程就是构建一个新系统的过程。想要制作出好用的 BSP,需要对 RT-Thread 系统的构建过程有一定了解,建议先掌握以下四个方面的知识:
- 掌握 HC32 系列 BSP 的使用方法:可阅读 HC32 BSP 说明 中"使用教程"表格内的文档,例如 外设驱动使用教程 和 外设驱动介绍;了解外设驱动的添加方法可参考《外设驱动添加指南》。
- 了解 Scons 工程构建方法:RT-Thread 使用 Scons 作为系统的构建工具,掌握 Scons 的常用命令(如
scons、scons --target=mdk5等)是制作新 BSP 的基本要求。 - 了解设备驱动框架:在 RT-Thread 系统中,应用程序通过设备驱动框架来操作硬件,了解设备驱动框架对添加 BSP 驱动很重要。
- 了解 Kconfig 语法:RT-Thread 系统通过 menuconfig 方式进行配置,menuconfig 中的选项由 Kconfig 文件决定,因此需要对 Kconfig 语法有一定了解。
3. BSP 制作方法
本节以制作 XHSC 官方评估板ev_hc32f4a0_lqfp176的 BSP 为例,讲解如何为一个新的开发板添加 BSP。制作过程分为以下五个步骤:
- 复制 BSP 通用模板
- BOARD 配置
- 修改 BSP 中的 Kconfig 文件
- 修改构建工程相关文件
- 重新生成工程
3.1 复制 BSP 通用模板
制作新 BSP 的第一步是复制一份同系列的 BSP 作为基础,通过对 BSP 的修改来获得新 BSP。本次示例制作的 BSP 为 HC32F4A0 系列,因此直接复制同系列的ev_hc32f4a0_lqfp176文件夹,并将该文件夹名称改为所要制作的 BSP 的名字(示例中保持原名不变)。
在 BSP 的制作过程中,主要修改board文件夹内的配置文件。下表总结了board文件夹中需要修改的内容:
| 项目 | 需要修改的内容说明 |
|---|---|
| linker_scripts (文件夹) | BSP 特定的链接脚本 |
| board.c/h | 系统时钟、GPIO 初始化函数、芯片存储器大小 |
| board_config.c/h | 外设管脚配置及初始化 |
| Kconfig | 芯片型号、系列、外设资源 |
| SConscript | 芯片启动文件、目标芯片型号 |
3.2 修改 board 配置
3.2.1 配置外设管脚
在board_config.c文件中实现了外设管脚相关配置。例如函数rt_hw_board_uart_init中配置了 USART1 和 USART6 的管脚,源码位于 board_config.c:
#if defined(RT_USING_SERIAL) rt_err_t rt_hw_board_uart_init(CM_USART_TypeDef *USARTx) { rt_err_t result = RT_EOK; switch ((rt_uint32_t)USARTx) { #if defined(BSP_USING_UART1) case (rt_uint32_t)CM_USART1: /* Configure USART RX/TX pin. */ GPIO_SetFunc(USART1_RX_PORT, USART1_RX_PIN, USART1_RX_FUNC); GPIO_SetFunc(USART1_TX_PORT, USART1_TX_PIN, USART1_TX_FUNC); break; #endif #if defined(BSP_USING_UART6) case (rt_uint32_t)CM_USART6: /* Configure USART RX/TX pin. */ GPIO_SetFunc(USART6_RX_PORT, USART6_RX_PIN, USART6_RX_FUNC); GPIO_SetFunc(USART6_TX_PORT, USART6_TX_PIN, USART6_TX_FUNC); break; #endif default: result = -RT_ERROR; break; } return result; } #endif其他外设(如 I2C、ADC、SPI、CAN、ETH、SDIO、QSPI、SDRAM 等)的管脚配置函数也以同样的模式组织在board_config.c中,均通过#if defined(BSP_USING_xxx)条件编译与 Kconfig 选项联动。
在board_config.h中实现了管脚的宏定义,可根据实际使用的管脚进行修改。以 USART1 为例,源码位于 board_config.h:
#if defined(BSP_USING_UART1) #define USART1_RX_PORT (GPIO_PORT_H) #define USART1_RX_PIN (GPIO_PIN_13) #define USART1_RX_FUNC (GPIO_FUNC_33) #define USART1_TX_PORT (GPIO_PORT_H) #define USART1_TX_PIN (GPIO_PIN_15) #define USART1_TX_FUNC (GPIO_FUNC_32) #endif每个外设宏均用对应的BSP_USING_xxx宏包裹,只有通过 menuconfig 使能了该外设,管脚宏才会生效。修改时只需将PORT、PIN、FUNC三要素替换为目标引脚的取值(引脚复用功能号需查阅芯片数据手册)。
3.2.2 配置系统时钟
在board.c文件中存放了函数SystemClock_Config(),该函数负责初始化系统时钟。源码位于 board.c,示例中系统主频配置为 240MHz,可根据实际使用进行修改:
/* PCLK0, HCLK Max 240MHz */ /* PCLK1, PCLK4 Max 120MHz */ /* PCLK2, PCLK3 Max 60MHz */ /* EX BUS Max 120MHz */ CLK_SetClockDiv(CLK_BUS_CLK_ALL, (CLK_PCLK0_DIV1 | CLK_PCLK1_DIV2 | CLK_PCLK2_DIV4 | CLK_PCLK3_DIV4 | CLK_PCLK4_DIV2 | CLK_EXCLK_DIV2 | CLK_HCLK_DIV1)); ... /* VCO = (8/1)*120 = 960MHz */ stcPLLHInit.u8PLLState = CLK_PLL_ON; stcPLLHInit.PLLCFGR_f.PLLM = 1UL - 1UL; stcPLLHInit.PLLCFGR_f.PLLN = 120UL - 1UL; stcPLLHInit.PLLCFGR_f.PLLP = 4UL - 1UL; ... CLK_SetSysClockSrc(CLK_SYSCLK_SRC_PLL);从源码可以看到,HC32F4A0 的时钟链路为:外部晶振(XTAL,8MHz)→ PLLH 倍频到 960MHz VCO → 分频得到 240MHz 系统主频,同时 PCLK1/PCLK4 最高 120MHz、PCLK2/PCLK3 最高 60MHz。若使能了 USB(BSP_USING_USBD/BSP_USING_USBH/RT_USING_CHERRYUSB),还会额外配置 PLLX 以产生 480MHz 供 USB 使用。此外board.c中还包含SystemBase_Config()(Flash Cache/预取配置)、PeripheralClock_Config()(外设时钟源)和PeripheralRegister_Unlock()(外设寄存器解锁)等函数。
3.2.3 配置 FLASH 和 RAM
在board.h文件中配置了 FLASH 和 RAM 的相关参数,这个文件中需要修改的是HC32_FLASH_SIZE和HC32_SRAM_SIZE这两个宏控制的参数。源码位于 board.h:
#define HC32_FLASH_ERASE_GRANULARITY (8 * 1024) #define HC32_FLASH_WRITE_GRANULARITY (4) #define HC32_FLASH_SIZE (2 * 1024 * 1024) #define HC32_FLASH_START_ADDRESS (0) #define HC32_FLASH_END_ADDRESS (HC32_FLASH_START_ADDRESS + HC32_FLASH_SIZE) #define HC32_SRAM_SIZE (512) #define HC32_SRAM_END (0x1FFE0000 + HC32_SRAM_SIZE * 1024)示例中 HC32F4A0 的 Flash 为 2MB、SRAM 为 512KB。制作新 BSP 时必须按照目标芯片的实际存储器容量修改这两个宏,HC32_SRAM_END会被后续堆内存配置所引用。
3.2.4 配置堆内存
通常情况下,系统 RAM 中的一部分内存空间会被用作堆内存。board.h中通过条件编译,在不同编译器下规定堆内存的起始地址HEAP_BEGIN和结束地址HEAP_END:
#ifdef __ARMCC_VERSION extern int Image$$RW_IRAM2$$ZI$$Limit; #define HEAP_BEGIN (&Image$$RW_IRAM2$$ZI$$Limit) #elif __ICCARM__ #pragma section = "HEAP" #define HEAP_BEGIN (__segment_end("HEAP")) #else extern int __bss_end; #define HEAP_BEGIN (&__bss_end) #endif #define HEAP_END HC32_SRAM_END这里HEAP_BEGIN和HEAP_END的值需要和后面 3.4.1 修改链接脚本 章节所修改的配置相一致:
- MDK(ARMCC):堆起始取链接脚本中
RW_IRAM2区域的 ZI 段结束地址; - IAR:取名为
HEAP的段结尾; - GCC:取
__bss_end。
在某些系列的芯片中,芯片 RAM 可能分布在不连续的多块内存区域上。此时堆内存的位置可以和系统内存在同一片连续的内存区域,也可以存放在一片独立的内存区域中,只需保证HEAP_BEGIN/HEAP_END与链接脚本对应区域的边界一致即可。
3.2.5 配置中断
在irq_config.h文件中,统一分配了外设使用的中断号。HC32F4A0 系列的中断可灵活配置,用户可根据实际使用情况灵活调整外设中断号。源码位于 irq_config.h,例如:
#define BSP_EXTINT0_IRQ_NUM INT022_IRQn #define BSP_EXTINT0_IRQ_PRIO DDL_IRQ_PRIO_DEFAULT /* DMA1 ch0 */ #define BSP_DMA1_CH0_IRQ_NUM INT038_IRQn #define BSP_DMA1_CH0_IRQ_PRIO DDL_IRQ_PRIO_DEFAULT每个中断都以BSP_xxx_IRQ_NUM(中断向量号)和BSP_xxx_IRQ_PRIO(中断优先级)成对定义,驱动源码通过这两个宏向 RT-Thread 的中断管理注册中断服务函数。由于 HC32 系列的中断号可以在一定范围内灵活映射,制作新 BSP 时应避免中断号冲突,并按需调整优先级。
3.3 修改 Kconfig 选项
本小节需要修改board/Kconfig文件的内容,主要有两点:
- 芯片型号和系列
- BSP 上的外设支持选项
芯片型号和系列的修改如下表所示(对应 board/Kconfig 中的配置):
| 宏定义 | 意义 | 格式 |
|---|---|---|
| SOC_HC32F4A0SI | 芯片型号 | SOC_HC32xxx |
| SOC_SERIES_HC32F4 | 芯片系列 | SOC_SERIES_HC32xx |
实际 Kconfig 中的写法为:
config SOC_HC32F4A0SI bool select SOC_SERIES_HC32F4 select RT_USING_COMPONENTS_INIT select RT_USING_USER_MAIN default y其中SOC_HC32F4A0SI为芯片型号选择项,select SOC_SERIES_HC32F4将其归入 HC32F4 系列,同时自动勾选组件自动初始化和 user main。
关于 BSP 上的外设支持选项:一个初次提交的 BSP 仅仅需要支持 GPIO 驱动和串口驱动即可,因此在配置选项中只需保留这两个驱动配置项。GPIO 与串口在 Kconfig 中的定义如下:
config BSP_USING_GPIO bool "Enable GPIO" select RT_USING_PIN select BSP_USING_TCA9539 default y menuconfig BSP_USING_UART bool "Enable UART" default y select RT_USING_SERIAL if BSP_USING_UART menuconfig BSP_USING_UART1 bool "Enable UART1" default y if BSP_USING_UART1 config BSP_UART1_RX_USING_DMA bool "Enable UART1 RX DMA" depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA default n config BSP_UART1_RX_BUFSIZE int "Set UART1 RX buffer size" range 64 65535 depends on RT_USING_SERIAL_V2 default 256 ... endif ... endif注意 Kconfig 中每个外设选项都会select对应的 RT-Thread 组件(例如BSP_USING_GPIO自动勾选RT_USING_PIN,BSP_USING_UART自动勾选RT_USING_SERIAL),保证硬件驱动与上层框架联动。
3.4 修改工程构建相关文件
3.4.1 修改链接脚本
linker_scripts链接文件位于board/linker_scripts文件夹下,包含三种编译器使用的脚本:
下面以 MDK 使用的链接脚本link.sct为例,展示如何修改链接脚本。本次制作 BSP 使用的芯片为 HC32F4A0,FLASH 为 2M,因此修改LR_IROM1和ER_IROM1的参数为0x00200000;RAM 的大小为 512k,因此修改RW_IRAM2的参数为0x0007FFF8,起始地址为0x1FFE0008(RAM 起始地址 0x1FFE0000 处预留了 8 字节的 noinit 段)。实际 link.sct 内容如下:
LR_IROM1 0x00000000 0x00200000 { ; load region size_region ER_IROM1 0x00000000 0x00200000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x1FFE0000 UNINIT 0x00000008 { ; RW data *(.bss.noinit) } RW_IRAM2 0x1FFE0008 0x0007FFF8 { ; RW data .ANY (+RW +ZI) .ANY (RAMCODE) } RW_IRAMB 0x200F0000 0x00001000 { ; RW data .ANY (+RW +ZI) } }这样的修改方式在一般的应用下就够用了,后续如果有特殊要求(例如自定义存储区域划分),则需要按照链接脚本的语法根据实际需求修改。注意此处的区域边界必须与 3.2.4 节HEAP_BEGIN/HEAP_END的配置保持一致。
其他两个链接脚本分别为 IAR 使用的link.icf和 GCC 编译器使用的link.ld,修改的方式类似:
link.icf修改内容:link.ld修改内容:
3.4.2 修改构建脚本
SConscript脚本决定 MDK/IAR 工程的生成以及编译过程中要添加的文件。在这一步中需要修改芯片型号以及芯片启动文件的地址。实际 board/SConscript 内容如下:
import os from building import * cwd = GetCurrentDir() # add general drivers src = Split(''' board.c board_config.c ''') path = [cwd] path += [cwd + '/ports'] path += [cwd + '/config'] path += [cwd + '/config/usb_config'] CPPDEFINES = ['HC32F4A0', '__DEBUG'] group = DefineGroup('Drivers', src, depend = [''], CPPPATH = path, CPPDEFINES = CPPDEFINES) Return('group')其中CPPDEFINES = ['HC32F4A0', '__DEBUG']定义了目标芯片型号宏(启动文件和 DDL 库会依赖该宏选择芯片型号),编译路径则包含了ports、config等配置目录。
注意:若复制的是同系列的 BSP 来进行修改,这个文件可不做修改。
3.4.3 修改工程模板
template文件是生成 MDK/IAR 工程的模板文件,通过修改该文件可以设置工程中使用的芯片型号以及下载方式。MDK5/IAR 的工程模板文件位于 BSP 根目录下:
下面以 MDK5 模板的修改为例,介绍如何修改模板配置。首先修改芯片型号,使生成的工程与目标芯片匹配:
然后修改程序下载方式(例如选择对应的 J-Link/ST-Link 仿真器及 Flash 下载算法):
3.5 重新生成工程
重新生成工程需要使用 Env 工具(RT-Thread 官方提供的命令行配置环境)。
3.5.1 重新生成 rtconfig.h 文件
在 Env 界面输入命令menuconfig对工程进行配置,并生成新的 rtconfig.h 文件:
在配置界面中选择要打开的外设(例如使能 UART1、GPIO 等,初次提交的 BSP 只需保留 GPIO 与串口驱动):
3.5.2 重新生成 MDK/IAR 工程
下面以重新生成 MDK 工程为例,介绍如何重新生成 BSP 工程。
使用 Env 工具输入命令scons --target=mdk5重新生成工程:
重新生成工程成功后的输出如下:
到这一步为止,新的 BSP 就可以使用了。
接下来我们可以分别使用命令scons --target=mdk4、scons --target=iar、scons --target=eclipse,来更新 MDK4、IAR 和 GCC 的工程,使得该 BSP 变成一个完整的、可以提交到 GitHub 的 BSP(MDK4 工程的制作为可选)。
4. 规范
本章节介绍 RT-Thread HC32 系列 BSP 制作与提交时应当遵守的规范。开发人员在 BSP 制作完成后,可以根据本规范提出的检查点对制作的 BSP 进行检查,确保 BSP 在提交前有较高的质量。
4.1 BSP 制作规范
HC32 BSP 的制作规范主要分为 3 个方面:工程配置、ENV 配置和 IDE 配置。在已有的 HC32 系列 BSP 的模板中,已经根据下列规范对模板进行配置。在制作新 BSP 的过程中,拷贝模板进行修改时,需要注意不要修改这些默认的配置。BSP 制作完成后,需要对新制作的 BSP 进行功能测试,功能正常后再进行代码提交。
4.1.1 工程配置
遵从 RT-Thread 编码规范,代码注释风格统一;
main 函数功能保持一致:如果有 LED 的话,main 函数里只放一个LED 1HZ 闪烁的程序。仓库中 main.c 即为典型示例:
int main(void) { /* set LED_GREEN_PIN pin mode to output */ rt_pin_mode(LED_GREEN_PIN, PIN_MODE_OUTPUT); while (1) { rt_pin_write(LED_GREEN_PIN, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(LED_GREEN_PIN, PIN_LOW); rt_thread_mdelay(500); } }在
rt_hw_board_init中需要完成堆的初始化:调用rt_system_heap_init;默认只初始化 GPIO 驱动和 FinSH 对应的串口驱动,不使用 DMA;
当使能板载外设驱动时,应做到不需要修改代码就能编译下载使用;
提交前应检查 GCC/MDK/IAR 三种编译器直接编译或者重新生成后编译是否成功;
使用
dist命令对 BSP 进行发布,检查使用dist命令生成的工程是否可以正常使用。
4.1.2 ENV 配置
- 系统心跳统一设置为 1000(宏:
RT_TICK_PER_SECOND); - BSP 中需要打开调试选项中的断言(宏:
RT_DEBUG); - 系统空闲线程栈大小统一设置为 512(宏:
IDLE_THREAD_STACK_SIZE); - 开启组件自动初始化(宏:
RT_USING_COMPONENTS_INIT,在board/Kconfig的SOC_HC32F4A0SI中通过select默认开启); - 需要开启 user main 选项(宏:
RT_USING_USER_MAIN,同样由 Kconfig 默认 select); - FinSH 默认只使用 MSH 模式(宏:
FINSH_USING_MSH_ONLY)。
4.1.3 IDE 配置
- 使能下载代码后自动运行;
- 使能 C99 支持;
- 使能 One ELF Section per Function(MDK);
- MDK/IAR 生成的临时文件分别放到 build 下的 MDK/IAR 文件夹下;
- MDK/GCC/IAR 生成 bin 文件名字统一成
rtthread.bin。
4.2 BSP 提交规范
- 提交前请认真修改 BSP 的 README.md 文件,README.md 文件的外设支持表单只填写 BSP 支持的外设,可参考其他 BSP 填写。查看文档 HC32 系列驱动介绍 了解驱动分类。
- 提交 BSP 分为 2 个阶段提交:
- 第一阶段:基础 BSP 包括串口驱动和 GPIO 驱动,能运行 FinSH 控制台。完成 MDK4、MDK5、IAR 和 GCC 编译器支持,如果芯片不支持某款编译器(比如 MDK4)可以不用做。BSP 的 README.md 文件需要填写第二阶段要完成的驱动。
- 第二阶段:完成板载外设驱动支持,所有板载外设使用 menuconfig 配置后就能直接使用。若开发板没有板载外设,则此阶段可以不用完成。不同的驱动要分开提交,方便 review 和合并。
- 只提交 BSP 必要的文件,删除无关的中间文件,能够提交的文件请对照其他 BSP。
- 提交前要对 BSP 进行编译测试,确保在不同编译器下编译正常。
- 提交前要对 BSP 进行功能测试,确保 BSP 在提交前符合工程配置章节中的要求。
5. 小结
制作一个 HC32 系列 BSP 的核心流程可以概括为"复制模板 → 修改 board 配置 → 修改 Kconfig → 修改构建文件 → 重新生成工程"五个环节。只要严格按照 HC32 系列 BSP 制作教程 中的步骤操作,并遵守工程配置、ENV 配置、IDE 配置以及分阶段提交的规范,就能快速、规范地为新的 HC32 芯片或开发板产出可用的 BSP。在制作过程中,board目录下的 board.c、board.h、board_config.c、board_config.h、Kconfig、SConscript 以及linker_scripts下的三个链接脚本是最关键的修改对象,建议以仓库中现有的多个 HC32 BSP 为参照进行比对修改,以确保配置一致性与可维护性。
【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考