移植 ZephyrRTOS 到 G32R501¶
本文首发在 21IC BBS 论坛,这里是个备份,也做了些调整。
前言¶
关于 Zephyr¶
我称之为当下全球最火爆的 RTOS 项目,官网:https://www.zephyrproject.org。以下引用来自官网的自述:
What is the Zephyr Project? The Zephyr® Project is an open source scalable real-time operating system (RTOS) supporting multiple hardware architectures including ARC, ARM, RISC-V and X86. The Zephyr OS is a proven RTOS used in products today. Zephyr OS is modular and supports multiple architectures, developers can easily tailor an optimal solution to meet their needs. Zephyr OS is used in a broad spectrum of applications from simple connected sensors to complex edge systems. As an open source project, the community contributes to the evolution of Zephyr by adding support for new hardware, developer tools, sensors and device drivers. Enhancements in security, device management capabilities, connectivity stacks and file systems are easily implemented.
这个项目目前是托管在 Apache 基金会的一个开源 RTOS 项目。项目成员看包括多个知名的半导体厂商:Intel、Nordic、NXP、瑞萨、英飞凌、TI、ST 等,还有 Meta、Google 等知名互联网公司,我还在成员页看到 Honda ,没错就是本田。 这么多企业参与到其中,肯定是看好 Zephyr 这个项目本身。那这个项目的优势是啥呢?我自己的总结如下:
- 开源:项目的参与者都可以用代码发声。充分利用社区的同时,也能向社区注入自己的影响,还能给自己的产品增加曝光度。
- 基于 Devicetree+Kconfig 的开发模式,类似 Linux 开发,可以灵活地在多个型号 MCU 间实现最大程度的兼容。
- 丰富的生态。八种处理器架构、750+开发板、220+传感器驱动,还有官方集成好的加密库,图形库,多种协议栈……失眠常见的几乎都有。
单是最后这一项,我就强烈推荐这个 RTOS。
关于 G32R501¶
极海微最新推出的,基于 Cortex-M52 的实时控制 MCU,官网:https://www.geehy.com。下面是引用自官网对这个 MCU 的介绍:
G32R5系列实时控制MCU搭载Arm v8.1-M架构的Arm® Cortex®-M52内核及自研紫电数学指令扩展单元,支持基于矢量扩充方案(MVE)的Arm HeliumTM技术,集成高性能感知,控制外设和灵活的外设互联系统,支持-40°C~105°/125°C的宽环境工作温度,适用于新能源逆变器、商业电源、工业自动化、新能源汽车等广泛领域。

本次移植的目的是把 Zephyr 带入 G32R501,让极海的新 MCU 也享用来自全球的热度~~~
移植前的一些说明¶
本文是移植指南,文中也会提及 Zephyr 的相关概念,但并不能全面的介绍 Zephyr 开发的基本知识,这一部分内容请参考官网文档,点此直达。
构建工具¶
Zephyr 使用 west+CMake 作为构建工具,开发工程师也需要具备的相关知识。west 是 Zephyr 项目的配套工具,Zephyr 官方文档也有相应说明。CMake 的部分,请自学 OMG~
编译器¶
关于编译器,有需要注意的点有:
-
对于 Zephyr 项目 ARM GCC 编译器的支持是相对完善的。对于 Cortex-M52 内核,需要使用最新的是 14.2,可以从这里下载:https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads。
-
ARM Clang 编译器也是得到 Zephyr 的支持,但是我在移植过程中遇到了一些问题。虽然也能解决,但是调试确实是一个麻烦事。暂时不推荐~
-
IAR 的工具链也是 Zephyr 支持,但同样在移植 G32R501 的过程中也遇到一些麻烦。需要重点关注的是,Zephyr 使用的 IAR 编译器是 IAR Build Tools for Arm,而不是常见的 workbench。可以从 https://github.com/iarsystems/arm/releases 这里下载使用。
基本概念¶
下面再介绍一些 Zephyr 开发的基本概念,便以后面的移植工作。
SOC¶
先介绍以下 Zephyr 关于 MCU 架构的一些规定。在 Zephyr 中有如下几个概念与具体的 MCU 相关,需要移植工程师关注:
- SOC
- SoC series
- SoC family
- CPU Cluster
- CPU core
- Architecture
具体参考官方文档 SoC Porting Guide 一节。官方文档中还有一张图和一个表格有助于理解上的几个概念,连接在此 :

对应到移植对象 G32R501 应该是这样:
| 概念 | 对应内容 |
|---|---|
| architecture | Armv8.1-M |
| CPU core | Cortex-M52 |
| SoC family | Geehy G32R |
| SoC Series | G32R5 |
| SoC | G32R501 |
| board qualifiers | G32R501 |
| board name | G32R501 EVAL Board /G32R501 Micro-EVB |
G32R501 是基于 Cortex-M52,Zephyr 暂时还不支持。从这个层次开始移植,工程量非常巨大,我个人也没有能力完成。已知 Cortex-M52 属于 Armv8.1-M 一系,同系中 Zephyr 支持 Cortex-M55,我们就以此为基础修改,不再移植与处理器架构方面的内容。
Module¶
Module 可以理解为一个功能相对完整的”包“,可以是某个 lib 或者 SDK 。Module 遵循一定的规则组织提供配置、编译规则,可以通过 west 工具加入构建项目,如有必要也提供 Kconfig 等选项,具体可参考官方文档中Modules (External projects) 部分。
准备工作¶
一些约定¶
为了便于描述后续的移植工作,先约定一些内容。
- 工作目录:源码相关的目录都集中放在这个目录,设置环境变量 WORK_DIR 就是这个目录的路径。这个目录可以是其他目录,强烈建议目录路径不要有空格或者中文。我的机器上这个目录是 D:\zephyrproject。
- 必须的 Python 环境,使用虚拟环境,操作步骤后续会说明。
- Python 虚拟环境以及其他非源码相关的内容,统一放置在 D:\zephyrproject.asset 目录。与工作目录分开,也是为了保持工作目录整洁~同样的,这个目录也不是强制要求,可根据实际情况确定。
准备工作¶
准备工作就是搭建开发环境了,步骤是这样的:
- 安装依赖的软件工具
- 创建 Python 虚拟环境
- 安装 west
- 初始化工作目录
- 提高工作效率的小技巧
- 验证开发环境
下面详细说明搭建步骤:
第一步,安装依赖的软件工具¶
可参考官方文档 Install dependencies 一节。特别需要说明的时,如果你电脑上已经安装有 Python 但是版本低于 3.12,也建议安装 3.12 版本,后续我们会使用虚拟环境,多个版本的 Python 不影响开发工作。
第二步,创建 Python 虚拟环境¶
打开命令行窗口(cmd 或者 Powershell 都行,符合你习惯就好~后面没有特殊说明,都统一用命令行窗口指代)执行以下命令,并等待执行完成:
python -m venv D:\zephyrproject.asset\.venv
D:\zephyrproject.asset.venv 这就是虚拟环境所在目录。其父级目录就是 D:\zephyrproject.asset 前文约定3的内容。
使用 Python 肯定就会安装一些软件包,我们使用国内的软件源会方便一些,在命令行执行:
pip config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
上述是清华大学的软件园地址,你可以更换为其他源:
- 阿里云镜像源: http://mirrors.aliyun.com/pypi/simple
- 中国科学技术大学镜像源: https://mirrors.ustc.edu.cn/pypi/simple
- 华为云镜像源: https://repo.huaweicloud.com/repository/pypi/simple
- 腾讯云镜像源: https://mirrors.cloud.tencent.com/pypi/simple
第三步,安装 west¶
移植的工作,需要在 Python 虚拟环境里操作,所以先要在命令行工具里激活虚拟环境。 如果使用 cmd ,请执行:
D:\zephyrproject.asset\.venv\activate.bat
如果使用 Powershell ,请执行:
D:\zephyrproject.asset\.venv\Activate.ps1
无论是 cmd 或者 Powershell,执行成功后命令提示符前都会多一个 (.venv),例如我用的是 Powershell 执行后的是这样的:

注意绿色的文字。cmd 窗口不会有颜色,也没有 PS 这两个字~
激活虚拟环境后,就可以使用 pip 安装 west。在命令行窗口执行:
pip install west
安装完成后,可以执行一下 west -V 命令,查看版本,正常输出版本号就表明安装正常。下面我们使用 west 初始化工作目录。
第四步,初始化工作目录¶
west init D:\zephyrproject
D:\zephyrproject 就是约定的工作目录。如果与你自己的工作目录不一致,请使用你的工作目录路径。
上面的命令会从 Github 拉取 zephyr 源码,请确保网络正常~
这个命令顺利执行后,我们就拥有了 zephyr 的源码。除此之外,还需要其他的 module,请执行下面的命令:
cd D:\zephyrproject
west update
这个命令也会从 Github 拉取 Module 的源码。命令执行完成后,工作目录也就初始化完成。
第五步,提高工作效率的小技巧¶
正式开始工作前,我们还需要设定一些环境变量,激活虚拟环境。每次都敲很多命令确实也挺麻烦的。所以我准备了一个脚本,用于启动命令行时配置开发环境。也分为 cmd 和 Powershell 两种,请根据你的命令行工具选择一个。
先上 cmd 的方法:
新建脚本 D:\zephyrproject.asset\setup_env.bat:
@echo off
REM 设置环境变量,注意对应实际路径
set "ZEPHYR_BASE=D:\zephyrproject\zephyr"
set "ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb"
set "GNUARMEMB_TOOLCHAIN_PATH=D:\apps\arm-gnu-toolchain-13.2.Rel1-mingw-w64-i686-arm-none-eabi"
REM 把各个工具的可执行文件路径加入 PATH,如果它们不在 PATH 里~
set Path=C:\Program Files\Git\cmd;C:\Program Files\CMake\bin;C:\ProgramData\chocolatey\bin;%GNUARMEMB_TOOLCHAIN_PATH%\bin;C:\Program Files\SEGGER\JLink;%Path%
REM 激活虚拟环境,注意实际路径
call D:\zephyrproject.asset\.venv\Scripts\activate.bat
REM 显示各个工具版本号,以确认工具可用~
cmake --version
ninja --version
python -V
west --version
每次启动还需要输入这个脚本的路径,也不够便利,还有一招:“一键创建开发环境”。请按以下步骤操作:
- 打开文件管理器,切换到 D:\zephyrproject.asset 目录。
- 鼠标右键点击目录的空白处,选择【新建】→【快捷方式】。
- 在“请键入对象的位置”写 cmd 三个字,点击【下一步】。
- 在”键入该快捷方式的名称“处写 zephyrproject,点击【完成】。
- 回到文件夹里,能看到一个快捷方式,鼠标选中这个快捷方式。
- 点击右键选择【属性】,在”目标“处填入C:\Windows\System32\cmd.exe /K D:\zephyrproject.asset\setup_env.bat,在”起始位置“处填入D:\zephyrproject,点击【确定】。
完成后是这一个样子:

现在,鼠标双击这个快捷方式,就是一个配置好的 zephyr 开发环境了。这个快捷方式可以复制到其他地方,比如桌面~
下面说说 Powershell 的方法:
Powershell 的脚本 D:\zephyrproject.asset\setup_env.ps1:
# 设置环境变量,注意对应实际路径
$env:ZEPHYR_BASE = "D:\zephyrproject\zephyr"
$env:ZEPHYR_TOOLCHAIN_VARIANT = "gnuarmemb"
$env:GNUARMEMB_TOOLCHAIN_PATH = "D:\apps\arm-gnu-toolchain-13.2.Rel1-mingw-w64-i686-arm-none-eabi"
# 把各个工具的可执行文件路径加入 PATH,如果它们不在 PATH 里~
$env:Path = "C:\Program Files\Git\cmd;C:\Program Files\CMake\bin;C:\ProgramData\chocolatey\bin;$env:GNUARMEMB_TOOLCHAIN_PATH\bin;C:\Program Files\SEGGER\JLink;$env:Path"
# 激活虚拟环境,注意实际路径
$venvPath = "D:\zephyrproject.asset\.venv\Scripts"
$activateScript = Join-Path -Path $venvPath -ChildPath "Activate.ps1"
& $activateScript
# 显示各个工具版本号,以确认工具可用~
cmake --version
ninja --version
python -V
west --version
创建快捷方式的步骤:
- 打开文件管理器,切换到 D:\zephyrproject.asset 目录。
- 鼠标右键点击目录的空白处,选择【新建】→【快捷方式】。
- 在“请键入对象的位置”写 powershell ,点击【下一步】。
- 在”键入该快捷方式的名称“处写 zephyrproject_ps,点击【完成】。
- 回到文件夹里,能看到一个快捷方式,鼠标选中这个快捷方式。
- 点击右键选择【属性】,在”目标“处填入C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoExit -File "D:\zephyrproject.asset\setup_env.ps1",在”起始位置“处填入D:\zephyrproject,点击【确定】。 完成后是这一个样子:

双击两个快捷方式,各自出现的窗口应该类似这样:

左边是 cmd,右边是 Powershell。需要提醒的是,我的系统安装了 Windows Terminal,这个终端会把 cmd 和 Powershell 都“装”在一个框里,与传统的命令行窗口不太一样,请忽略这个差异~
第六步,验证开发环境¶
开发环境搭建好了,除了向上面脚本末尾显示各个软件的版本号,我们还可以试着编译一个工程,看看必备的东西是否都齐全。 打开命令行(可以用上前面准备的快捷方式了\~\~\~),执行:
west build -p -b stm32_min_dev@blue zephyr\samples\hello_world
如果一切正常,你应该能看到类似的编译结果:

这个命令是编译一个程序:led 闪烁,开发板就是淘宝上常见的 stm32f103xb 开发板:

编译成功,你可以烧录看看运行效果。选择 j-link 进行烧录:
west flash -r jlink
至此,开发环境搭建完成,后续的内容就是实际的移植教程了。
移植-第一部分¶
根据本人的开发经验,把 zephyr 移植到一个全新的 MCU,大致的流程是:
- 创建基本的文件(夹)结构。
- 修改并确保能编译通过。
- 增加必要的驱动。
- 继续完善驱动,直至满足项目需求。
其中,第一步是非常难的。因为构建一个 zephyr 的步骤比较多,涉及的工具也不少,每个工具对输入的要求不一而足,导致这个构建系统事实上是一个比较繁杂的过程。也希望我的这些经验能帮助有需要的工程师,快速上手移植工作。
先介绍一个小贴士:在移植中完成“修改”→“编译验证”→“再修改”→“再验证”这样的“移植循环”,本质是:每个相对完整的修改,都要尽快编译验证。
第三步增加必要的驱动后,我们就可以点个灯之类的简单任务,剩余的就是测试和完善。
整个移植过程,会通过 git 进行管理,当然也会开放出来,供有需要的工程师使用。需要提前声明的一点是:这份移植说明和相关的代码,是本人再工作之余花费时间和精力总结、编写,未能对全部内容进行充分验证。如果要把这些内容使用到产品开发,请自行验证以确保各项功能完备。本人不提供任何担保,不承担因使用软件而产生的任何直接或间接损害的责任。
起手式¶
也就是第一部分的移植工作:创建基本的文件(夹)结构。由于涉及的内容较多,请按说明步骤完成。
前文提到,这个移植工作是以 module 形式组织,所以第一步就是创建符合 module 的文件结构。在工作目录(我这里是 D:\zephyrproject,下文不再赘述)创建文件夹 g32r5_zephyr 文件夹。移植代码就在这个文件夹完成,我们称之为“移植目录”,最开始的文件(夹)是这样的:
D:\zephyrproject
├─g32r5_zephyr
├─zephyr
├─module.yml
D:\zephyrproject\g32r5_zephyr\zephyr\module.yml 这个文件目前就只需要一行内容:
name: hal_g32r5
遵循“移植循环”,我们先验证这个 module 是不是有效的。进入开发环境(简单地说就是打开前文所述两个快捷方式的其中一个),切换到工作目录,执行一次编译:
west build -p -b stm32_min_dev@blue zephyr\samples\hello_world -- -DZEPHYR_EXTRA_MODULES="D:\zephyrproject\g32r5_zephyr
这个编译命令与前文”第六步,验证开发环境“的命令类似,不同的是增加了” -- -DZEPHYR_EXTRA_MODULES="D:\zephyrproject\g32r5_zephyr“。增加的部分就是告诉构建工具(west),有一个附加的 module 需要处理。
如果一切正常,命令执行后 build 目录下有 zephyr_modules.txt 这样一个文件。你能在这个文件中找到类似下面这一行:
"hal_g32r5":"D:/zephyrproject/g32r5_zephyr":""
这样的内容表明我们确实创建了一个能被 west 识别的、完备的 module,虽然这个 module 没有任何实质内容:)
迈出的第一步,是成功的!值得庆贺。
创建一个简单的测试程序¶
为了方便移植工作,我们先加入一个简单的程序以便后续移植过程中可随时修改而不会改动 zephyr 源码。
在移植目录中创建 test 文件夹,把 zephyr\samples\hello_world 文件夹下的全部内容复制到这里。
另外,在后续的修改过程中大部分都需要清除现有构建内容重新配置、构建,为了避免每次都需要输入冗长的构建命令,我会在移植目录中创建一个脚本 do_build.bat 专用于启动重新构建:
@echo off
setlocal enabledelayedexpansion
set "CURRENT_PATH=%~dp0"
set "PARENT_PATH=!CURRENT_PATH:~0,-1!"
set "PARENT_PATH=!parent_path:\=/!"
set "PROJECT_ROOT=%parent_path%/test"
set "BOARD=stm32_min_dev@blue"
echo ZEPHYR_EXTRA_MODULES: !PARENT_PATH!
echo ZEPHYR_TOOLCHAIN_VARIANT: !ZEPHYR_TOOLCHAIN_VARIANT!
REM Delete build directory
del build /q /f /s >NUL 2>&1
west build -p -b stm32_min_dev@blue !PROJECT_ROOT! -- -DZEPHYR_EXTRA_MODULES=!PARENT_PATH!
endlocal
好了,现在工作目录,只需要执行:
g32r5_zephyr\do_build.bat
就可以进行一次全新的配置、构建:

后续的移植工作中,我们会加入配套开发板的移植内容,到那时只需要修改 do_build.bat 文件的 BOARD 一行就可以重新构建~
增加 soc 相关内容¶
这一部分移植过程中最难的一步,准备的文件比较多,类型(格式)也相对繁杂。因为是一个整体的移植部分,若是每次修改或者增加文件都编译一次再排错,文章就会过于冗长。所以本节内容工会在整体介绍完成后才进行编译测试。
我会为这一个阶段移植的步骤增加标题增加序号,以便阅读。
1. 修改 g32r5_zephyr\zephyr\module.yml 文件¶
修改这个文件的目的是让 west 构建工具知晓移植目录里有新增的 soc 需要加入构建。修改后的文件应该是这样:
name: hal_g32r5
build:
cmake: .
kconfig: Kconfig
settings:
board_root: .
dts_root: .
soc_root: .
相较于之前的内容,新增了 build 及后续行。目的是告诉 west 工具,当前目录(其实是移植目录,这里很关键!)中有开发板和 soc 的相关内容,构建期间需要与 zephyr 源码目录的相关内容一起处理~
2. 增加 soc 目录¶
在移植目录下,创建 soc 两个目录,文件见名称不要随意修改,否则 west 工具会找不到~
soc 目录下的完整文件/文件夹是这样的:
│ CMakeLists.txt
│
└─geehy
└─g32r5
│ CMakeLists.txt
│ Kconfig
│ Kconfig.defconfig
│ Kconfig.soc
│ linker.ld
│ soc.yml
│
├─common
│ CMakeLists.txt
│ pinctrl_soc.h
│
└─g32r501
CMakeLists.txt
Kconfig.soc
soc.c
soc.h
文件不少,下文会重点介绍文件的用途和关键内容,完整内容请直接查看源码。
→ g32r5_zephyr\soc\CMakeLists.txt
这个文件是说明 soc 目录如何加入构建。其他 CMakeLists.txt 文件的用途相同,不再赘述。
→ g32r5_zephyr\geehy\g32r5
这个目录结构对应前文“基本概念”里的知识,如不理解,请返回阅读。
→ g32r5_zephyr\geehy\g32r5\Kconfig
这个文件定义了 SOC-Family 的配置项,也同时选定了依赖的项目,文件关键内容如下:
config SOC_FAMILY_G32R5
bool
select BUILD_OUTPUT_HEX
select USE_G32R5_HAL
select CPU_HAS_ARM_MPU
select CPU_HAS_MPU
select CPU_HAS_FPU
select ARMV8_M_DSP
以上内容的目的是:定义了 SOC_FAMILY_G32R5 及依赖配置项,也就是使用 G32R5 这个 SOC_FAMILY 的话,必须还要选择 select 条目列明的所有项。需要特别说明的是除 USE_G32R5_HAL 都是 zephyr 本身定义的配置项,USE_G32R5_HAL 这个项目是我们移植的一部分。
→ g32r5_zephyr\geehy\g32r5\Kconfig.defconfig
这个文件是可选的。它的目的是选定一些关于 G32R5 的可选项~有点拗口哈。当前可以保持是一个没有内容的空文件。
→ g32r5_zephyr\geehy\g32r5\Kconfig.soc
这个文件主要内容是:
config SOC_FAMILY
default "g32r5" if SOC_FAMILY_G32R5
config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default 240000000 if SOC_FAMILY_G32R5
rsource "*/Kconfig.soc"
config SOC_FAMILY 一行的作用是提供一个字符串类型的配置项,主要目的提供一个 CONFIG_SOC_FAMILY 的全局宏定义,配置,构建过程都会用到。
config SYS_CLOCK_HW_CYCLES_PER_SEC 一行定义了硬件系统时钟频率,这里先写一个固定的数值,后面可以修改为从 DeviceTree 的内容获取。
最后一行是遍历全部子目录中的 Kconfig.soc 文件,如果有的话~对于本项目的移植只有 g32r5_zephyr\geehy\g32r5\g32r501\Kconfig.soc 这个文件。
→ g32r5_zephyr\geehy\common
目录是 G32R5 这个 Family 公用的一些资源。
→ g32r5_zephyr\geehy\g32r5\g32r501
这个目录的内容也就是对应 g32r501 这个 soc-series。
→ g32r5_zephyr\soc\geehy\g32r5\g32r501\Kconfig.soc
定义 g32r501 这个 soc-series 以及这个 series 下具体的 soc,主要内容是:
config SOC_SERIES_G32R501
bool "G32R501 Series MCU"
select ARM
select SOC_FAMILY_G32R5
select CPU_HAS_FPU
select CPU_CORTEX_M55
select ARMV8_1_M_MVEI
select ARMV8_1_M_MVEF
help
Enable support for Geehy G32R501 MCU series
config SOC_SERIES
default "g32r501" if SOC_SERIES_G32R501
config SOC_G32R501DVXT
bool "G32R501DVXT"
select SOC_SERIES_G32R501
config SOC_G32R501DRXT
bool "G32R501DRXT"
select SOC_SERIES_G32R501
# ...
config SOC
default "g32r501dvxt" if SOC_G32R501DVXT
default "g32r501drxt" if SOC_G32R501DRXT
default "g32r501dmxt" if SOC_G32R501DMXT
default "g32r501dnxt" if SOC_G32R501DNXU
config FLASH_SIZE
default 256
config SRAM_SIZE
default 16
config FLASH_BASE_ADDRESS
default 0x08000000
config SRAM_BASE_ADDRESS
default 0x20000000
config NUM_IRQS
default 227
config SOC_SERIES_G32R501 定义了 G32R501 这个 SOC-SERIES(这个概念请回顾前文)。这里可以看出我们是基于 Cortex-M55 这个核而不是 Cortex-M52 ,之所以这样做,前文也有提到:zephyr 尚未支持 Cortex-M52,架构移植难度我目前尚未掌握。而 M52 和 M55 同处一系,只是某些特性有差异。而这一部分差异不影响目前的移植工作,所以就直接基于 M55 进行移植开发。文件的后续,定义了具体的 MCU 型号以及 Flash-Size 等必要的信息。
→ g32r5_zephyr\soc\geehy\g32r5\g32r501\soc.c/.h
这两个 c 源码是本系列 soc 的必须的基础源码,特别是 soc.h 一定不能省略,很多 zephyr 底层代码会包含这个头文件。
soc.h 文件不能缺,内容可以很简单:
#ifndef SOC_H_
#define SOC_H_
#include <g32r501.h>
#endif /* SOC_H_ */
没错,有效内容就一行,包含 SDK 中 G32R501 对应的头文件。
soc 目录的介绍到此未知,内容其实繁杂,可以结合代码仓库的实际文件加深理解。
3. 增加 dts 目录¶
dts 相关的文件,是 DeviceTree 的“配料表”,dtc 工具会根据一系列 dts 文件处理生成完整的 DeviceTree、相关头文件供后面的编译。
dts 文件除了 .dts 扩展名的文件外,还有 .dtsi 文件,类似 .h 文件之于 .c 文件。
在移植目录创建文件夹多级的文件夹:dts\geehy\g32r5 ,并在这个文件夹下创建文件 g32r501.dtsi 主要内容:
#include <arm/armv8.1-m.dtsi>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/adc/adc.h>
#include <freq.h>
#include <mem.h>
/ {
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-m52";
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
clock-frequency = <DT_FREQ_M(168)>;
mpu: mpu@e000ed90 {
compatible = "arm,armv8m-mpu";
reg = <0xe000ed90 0x40>;
};
};
};
};
&nvic {
arm,num-irq-priority-bits = <4>;
};
很简练的内容,也只说明了内核的一些信息。
4. 加入 SDK¶
这一步会把官方针对 G32R501 的 SDK 加入到工程中。由于没有找到官方的代码仓库,这里引用了 Gitee 上的一个仓库。
这里要补充一下,移植的工程是使用 git 管理。在移植目录执行命令可以创建 git 仓库:
git init .
然后使用这个命令引入 SDK:
git submodule add https://gitee.com/quincyzh/hal_geehy_g32r501.git sdk/g32r501
成功拉取代码后,移植目录会出现 sdk\g32r501 这个目录。
为了能使 SDK 能加入到编译中,我们需要增加两个 CMakeLists.txt 和两个 Kconfig 文件。两组文件的其中一组是在移植目录,另一组使在 sdk 目录。 移植目录下的 CMakeLists.txt:
# G32R5
add_subdirectory_ifdef(CONFIG_SOC_FAMILY_G32R5 sdk)
zephyr_include_directories_ifdef(CONFIG_SOC_FAMILY_G32R5 include)
# Kconfig
rsource "sdk/Kconfig"
sdk 目录下的 CMakeLists.txt:
# sdk/CMakeLists.txt
zephyr_library()
zephyr_compile_definitions(
-D__ARM_ARCH_8_1M_MAIN___
-D__ARM_TARGET_COPROC
-D__G32R501__
-D__G32R501XX__
-D__CORE_CPU0__
)
zephyr_include_directories(g32r501/device_support/g32r501/common/device/Geehy)
zephyr_include_directories(g32r501/device_support/g32r501/common/device/CMSIS/Core/Include)
zephyr_include_directories(g32r501/device_support/g32r501/common/device/Geehy/system_eval/include)
zephyr_include_directories(g32r501/driverlib/g32r501/driverlib)
zephyr_include_directories(g32r501/device_support/g32r501/headers/include)
file(GLOB_RECURSE HAL_SOURCES
g32r501/driverlib/g32r501/driverlib/*.c
)
# Remove some files
set(BLACKLIST_HAL_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/g32r501/driverlib/g32r501/driverlib/interrupt.c"
)
list(REMOVE_ITEM HAL_SOURCES ${BLACKLIST_HAL_SOURCES})
zephyr_library_sources(${HAL_SOURCES})
SDK 下的 Kconfig 文件是个空文件~保留它主要是为了后面增加功能。
这两个 CMakeLists.txt 文件把 SDK 目录下的相关文件加入到构建中,还增加了必要的宏定义。其中需要注意的有两点:
- 以 zephyr_ 开头的函数,均是 zephyr 扩展的内容而不是 cmake 内建函数。
- _ifdef 结尾的函数,会在执行构建检查,只有第一个参数代表的变量已经定义的情况下才会执行。而第一个参数由 Kconfig 工具依据 Kconfig 选项文件解析得到。
5. 增加开发板相关内容¶
我们移植的开发板是 G32R501 Micro-EVB:

文档在此,这个板子的主要资源有:
- 板载 CMSIS-DAP 兼容仿真器:Geehy-Link调试器 x1
- 外设资源:
- RESET KEY x1
- LED x2
- 40-pin ExpandPack接口 x1
移植步骤: 在移植文件夹,创建目录 boards\geehy\g32r501_micro_evb,新建如下文本文件:
board.yml
g32r501_micro_evb.dts
g32r501_micro_evb.yaml
g32r501_micro_evb_defconfig
Kconfig.defconfig
Kconfig.g32r501_micro_evb
各个文件的具体内容请参考代码仓库,这里介绍一下 g32r501_micro_evb.dts:
/dts-v1/;
#include <geehy/g32r5/g32r501.dtsi>
/ {
model = "Geehy G32R501 Eval";
compatible = "geehy,g32r501";
chosen {
zephyr,flash = &flash0;
};
};
文件的 include 一行,就包含了我们之前编写的 dtsi 文件,至此我们就可以进行编译了~
6. soc 的初始化¶
每个 MCU 都有一些必要的初始化工作,以确保复位后软件系统也能处于一个确定的状态。
对于 G32R501 soc 最基本的初始化工作必须包括:
- 使能 CP12 协处理器功能:G32R501 的“写寄存器保护(WRPRT)”功能依赖协处理器。
- 禁用看门狗:G32G501 看门狗默认是开启的,暂时先关闭~
具体可以参考 G32R501 的用户手册。
找到 soc\geehy\g32r5\g32r501\soc.c 文件,这个文件是个空文件,我们加入以下内容:
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include "soc.h"
#if defined(CONFIG_SOC_PREP_HOOK)
// execute before z_bss_zero and z_data_copy(C runtime initialize)
void soc_prep_hook(void)
{
// Enable CP0 Full Access and CP0 Non-secure Access
SCB->CPACR |= (3U << 0U*2U);
SCB->NSACR |= (3U << 0U*2U);
// Enable WRPRT Protection COP
SCB->CPACR |= 0xC;
__DSB();
__ISB();
// Disable the Watchdog Timer
__wrprt_disable();
WD->WDCR = (0x5U << WD_WDCR_WDCHK_Pos) |
(0x1U << WD_WDCR_WDDIS_Pos);
__wrprt_enable();
}
#endif /* CONFIG_SOC_PREP_HOOK */
soc.c 的主要功能就是上面所述的两点。
另外,从源码可以看出,这个函数是在宏 CONFIG_SOC_PREP_HOOK 已经定义的情况下才会被编译。前面提到过 CONFIG_ 这一类宏都是 Kconfig 工具生成的,所以要到 Kconfig 文件去处理,有两种方法可以使能这个宏:
- 一是在工程级别的 Kconfig 文件使能这个选项
- 二是在 soc 级别的 kconfig 文件中直接启用这个选项
因为这个特性是 soc 相关的,所以直接在 soc 相关文件直接启用这个选项,具体的方法是修改 soc\geehy\g32r5\g32r501\Kconfig.soc 这个文件:
config SOC_SERIES_G32R501
bool "G32R501 Series MCU"
select ARM
select SOC_FAMILY_G32R5
select CPU_HAS_FPU
select CPU_CORTEX_M55
select ARMV8_1_M_MVEI
select ARMV8_1_M_MVEF
select SOC_PREP_HOOK
在 SOC_SERIES_G32R501 这个选项下增加 select SOC_PREP_HOOK,这样就可以了~
最简答的 soc 的初始化工作完成,下面我们编译测试~
7. 简单测试¶
修改移植目录下 do_build.bat 文件中的 BOARD :
@echo off
setlocal enabledelayedexpansion
set "CURRENT_PATH=%~dp0"
set "PARENT_PATH=!CURRENT_PATH:~0,-1!"
set "PARENT_PATH=!parent_path:\=/!"
set "PROJECT_ROOT=%parent_path%/test"
set "BOARD=g32r501_micro_evb"
echo ZEPHYR_EXTRA_MODULES: !PARENT_PATH!
echo ZEPHYR_TOOLCHAIN_VARIANT: !ZEPHYR_TOOLCHAIN_VARIANT!
REM Delete build directory
del build /q /f /s >NUL 2>&1
west build -p -b !BOARD! !PROJECT_ROOT! -- -DZEPHYR_EXTRA_MODULES=!PARENT_PATH!
endlocal
是的,把 BOARD 改为我们定义的开发板名称:g32r501_micro_evb。
然后,执行这个脚本:

上图是我编译的结果,显然成功了。两个红色框显示已经使用了新定义的开发板。
这个编译成功的程序,是可以下载并执行的,虽然看不出任何动静。
启动调试,可以看到代码运行到 main 函数。不要小看进入到 main 的这个过程,zephyr 系统的启动过程其实还是比较复杂的,main 函数是系统内置的一个任务(task)。进入到 main 函数已经说明系统已经初始化完成,包括:C 运行时环境,zephyr 内核,已经定义好的驱动……所以,调试中能进入 main 证明我们上面的移植是成功的。
小结¶
移植工作进行到这里,最基本移植工作就算是完成了。
这一部分难度也是比较高:我们借用了 Cortex-M55 架构的内容,把 Zephyr 内核移植到 G32R501 这一新的 MCU。虽然时钟还未正确初始化,系统滴答频率肯定不准确,但是它真的跑起来了。接下来,我们需要移植更多的驱动程序:时钟、GPIO、UART 等,我们将在第二部分介绍。
最后,移植的内容已经公开在 gitee 的代码仓库。可以在代码仓库中查看“增加 soc 驱动”那一次提交。代码仓库: https://gitee.com/quincyzh/g32r5_zephyr
移植-第二部分¶
先点个灯~¶
经过前面的步骤,新移植的框架已经搭建完成。下面我们就开始点灯~当然是通过 zephyr 的驱动程序去点灯。和前一部分类似,我会在下文按操作步骤写上编号,以便读者查阅。
- 1、dts 文件引入 GPIO
- 2、设备描述文件 geehy,g32r5-gpio.yaml
- 3、GPIO 驱动程序
- 4、修改以实现闪灯
1、dts 文件引入 GPIO 相关内容¶
修改移植目录下的 dts\geehy\g32r5\g32r501.dtsi 文件,加入 GPIO 相关内容:
/*
* Copyright (c) 2025 Quincy.W <wangqyfm@foxmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <arm/armv8.1-m.dtsi>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/adc/adc.h>
#include <freq.h>
#include <mem.h>
/ {
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-m52";
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
clock-frequency = <DT_FREQ_M(240)>;
mpu: mpu@e000ed90 {
compatible = "arm,armv8m-mpu";
reg = <0xe000ed90 0x40>;
};
};
};
soc {
pinctrl: pin-controller@40030000 {
compatible = "geehy,g32r5-pinctrl";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x40030000 0xC00>;
gpioa: gpioa@40030000 {
compatible = "geehy,g32r5-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x40030000 0x80>;
};
gpiob: gpiob@40030080 {
compatible = "geehy,g32r5-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x40030080 0x80>;
};
};
};
};
&nvic {
arm,num-irq-priority-bits = <4>;
};
soc 那一部分是新增的。
修改移植目录 boards\geehy\g32r501_micro_evb\g32r501_micro_evb.dts 增加 LED 部分内容:
/dts-v1/;
#include <geehy/g32r5/g32r501.dtsi>
/ {
model = "Geehy G32R501 Eval";
compatible = "geehy,g32r501";
leds {
compatible = "gpio-leds";
led1: led1 {
gpios = <&gpioa 23 GPIO_ACTIVE_LOW>;
label = "LD 1";
};
led2: led2 {
gpios = <&gpioa 8 GPIO_ACTIVE_LOW>;
label = "LD 2";
};
};
aliases {
led0 = &led1;
led1 = &led2;
};
};
leds,aliases 为新增部分。这个时候,如果尝试进行编译,会出现类似下图中红色框的错误提示。这个错误出现的原因是,没有对应的驱动程序描述文件(嗯~这个名字是我胡乱起的,不知道是不是合适),为解决这个错误,请继续下一个步骤。

2、设备描述文件 geehy,g32r5-gpio.yaml¶
在移植目录,创建这个文件 dts\bindings\gpio\geehy,g32r5-gpio.yaml,当然相应的目录也需要新建。文件内容:
#
description: Geehy G32R5 GPIO controller
compatible: "geehy,g32r5-gpio"
include:
- name: gpio-controller.yaml
- name: base.yaml
properties:
reg:
required: true
"#gpio-cells":
const: 2
ngpios:
type: int
default: 32
gpio-cells:
- pin
- flags
添加了这个文件后,编译是正常了。但是我们知道,其实还是没有驱动程序的,请继续下一步。
3、GPIO 驱动程序¶
这一步,我们实现 G32R501 的 GPIO 驱动。
首先在移植目录创建子目录:drivers,并在该目录下创建子目录 gpio 和文件 CMakeLists.txt。
drivers\CMakeLists.txt 的内容是:
add_subdirectory_ifdef(CONFIG_GPIO gpio)
这里文件的有效内容就一行,也就是在 CONFIG_GPIO 生效的情况下,包含 gpio 目录到构建中~
在 drivers\gpio 下创建两个文件:CMakeLists.txt 和 gpio_g32r5.c。
drivers\gpio\CMakeLists.txt 文件的内容是:
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/gpio.h)
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_SOC_FAMILY_G32R5 gpio_g32r5.c)
这三行内容的作用是:
- 明确指定一个头文件的路径
- 本目录属于驱动
- 在 CONFIG_SOC_FAMILY_G32R5 定义的情况下 gpio_g32r5.c 加入构建。
drivers\gpio\gpio_g32r5.c 文件就是驱动文件的具体实现了:
/**
* @file drivers/gpio/gpio_g32r5.c
*/
#define DT_DRV_COMPAT geehy_g32r5_gpio
// ...
struct gpio_g32r5_config
{
// ...
};
struct gpio_g32r5_data
{
struct gpio_driver_data common;
};
// ...
static int gpio_g32r5_port_get_raw(const struct device *dev, uint32_t *value)
{
const struct gpio_g32r5_config *config =
(const struct gpio_g32r5_config *)dev->config;
volatile uint32_t *p_dat = (uint32_t *)(config->data_regs_base + GPDAT_OFFSET);
*value = *p_dat;
return 0;
}
static int gpio_g32r5_port_set_bits_raw(const struct device *dev,
gpio_port_pins_t pins)
{
const struct gpio_g32r5_config *config =
(const struct gpio_g32r5_config *)dev->config;
volatile uint32_t *p_set = (uint32_t *)(config->data_regs_base + GPSET_OFFSET);
*p_set = BIT(pins);
return 0;
}
static int gpio_g32r5_port_clear_bits_raw(const struct device *dev,
gpio_port_pins_t pins)
{
const struct gpio_g32r5_config *config =
(const struct gpio_g32r5_config *)dev->config;
volatile uint32_t *p_set = (uint32_t *)(config->data_regs_base + GPCLR_OFFSET);
*p_set = BIT(pins);
return 0;
}
static int gpio_g32r5_port_toggle_bits(const struct device *dev,
gpio_port_pins_t pins)
{
const struct gpio_g32r5_config *config =
(const struct gpio_g32r5_config *)dev->config;
volatile uint32_t *p_set = (uint32_t *)(config->data_regs_base + GPTOGGLE_OFFSET);
*p_set = pins;
return 0;
}
//
static inline int gpio_g32r5_configure(const struct device *dev,
gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_g32r5_config *config = dev->config;
volatile uint32_t *ptr;
WRPRT_DISABLE;
// ...
WRPRT_ENABLE;
return 0;
}
static int gpio_g32r5_port_set_masked_raw(const struct device *dev,
gpio_port_pins_t mask,
gpio_port_value_t value)
{
ARG_UNUSED(dev);
ARG_UNUSED(mask);
ARG_UNUSED(value);
return -ENOTSUP;
}
static int gpio_g32r5_pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
ARG_UNUSED(dev);
ARG_UNUSED(pin);
ARG_UNUSED(mode);
ARG_UNUSED(trig);
return -ENOTSUP;
}
static DEVICE_API(gpio, gpio_g32r5_api) = {
.pin_configure = gpio_g32r5_configure,
.port_get_raw = gpio_g32r5_port_get_raw,
.port_set_masked_raw = gpio_g32r5_port_set_masked_raw,
.port_set_bits_raw = gpio_g32r5_port_set_bits_raw,
.port_clear_bits_raw = gpio_g32r5_port_clear_bits_raw,
.port_toggle_bits = gpio_g32r5_port_toggle_bits,
.pin_interrupt_configure = gpio_g32r5_pin_interrupt_configure,
};
static int gpio_g32r5_init(const struct device *dev)
{
// const struct gpio_g32r5_config *config = dev->config;
return 0;
}
#define GPIO_G32R5_DEFINE(inst) \
static const struct gpio_g32r5_config gpio_g32r5_config##inst = { \
.ctrl_regs_base = DT_INST_REG_ADDR(inst), \
.data_regs_base = \
GPIODATA_BASE + (((DT_INST_REG_ADDR(inst) - GPIOCTRL_BASE) >> 7) << 4), \
}; \
\
static struct gpio_g32r5_data gpio_g32r5_data##inst; \
\
DEVICE_DT_INST_DEFINE(inst, gpio_g32r5_init, NULL, \
&gpio_g32r5_data##inst, &gpio_g32r5_config##inst, \
POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY, \
&gpio_g32r5_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_G32R5_DEFINE)
这里只保留了关键部分,以便说明驱动实现的方法,具体内容请参考代码仓库的源码。
驱动文件里,最主要的是 static DEVICE_API(gpio, gpio_g32r5_api) 以及 文件末尾的宏定义 DT_INST_FOREACH_STATUS_OKAY(GPIO_G32R5_DEFINE) 下面分别说明。
4、DEVICE_API¶
这个宏展开后实质就是:
static const struct gpio_driver_api gpio_g32r5_api = {
// ...
};
结构体 gpio_driver_api 的定义是在 zephyr\include\zephyr\drivers\gpio.h 里。查看源码可以发现浓烈的 Linux 驱动的味道~这个结构体的元素全是函数指针,从名字就大致能看出功能。我们所需要的做的就是填充这个结构体里的函数指针。举个例子 port_toggle_bits 这个是翻转某个位,在 G32R501 这颗 MCU 里硬件具备这个功能,就可以完成 gpio_g32r5_port_toggle_bits 这个函数,并在 gpio_g32r5_api 里给相应的元素赋值就可以。
移植过程中,有些函数指针必须要赋值,有些可以不用赋值保持为 0 …… 那么怎么判断哪些是必须要实现的呢?我暂时还没找到明确的依据,只能在 zephyr\include\zephyr\drivers\gpio.h 里看哪些函数指针会被直接调用,哪些是经过判断为 0 不再继续执行的~或者调试也可以判断。其他驱动程序也是怎样判断的。
5、修改以实现闪灯¶
前面的步骤,我们能正常编译程序。但是仍存在几个问题:1)APP 里没有闪灯的程序;2)系统时钟不正确;3)不能通过 west flash 命令下载。这三个问题的处理如下:
1)修改程序,加入闪灯功能¶
在 zephyr 源码目录下 samples\basic\blinky 是一个官方编写的闪灯程序,我们可以直接把 main.c 复制到移植目录 test\src 文件夹下,替换现有的 main.c。
2)修改 zephyr 系统滴答频率¶
两个修改点:
一是移植目录 dts\geehy\g32r5\g32r501.dtsi 文件,把 clock-frequency 改为 10MHz:
clock-frequency = <DT_FREQ_M(10)>;
二是移植目录 soc\geehy\g32r5\Kconfig.soc 文件,把 SYS_CLOCK_HW_CYCLES_PER_SEC 改为 10MHz:
config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default 10000000 if SOC_FAMILY_G32R5
3)增加下载固件的配置信息¶
在移植目录,新建文件 boards\geehy\g32r501_micro_evb\board.cmake,内容:
#
board_runner_args(pyocd "--target=g32r501dxx" "--frequency=10000000")
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
这个文件会告诉 west 程序,使用 pyocd 下载固件,参数是 board_runner_args 一行。
上述三步骤完成后,进行一次全新编译:
g32r5_zephyr\do_build.bat
再通过 west 下载固件:
west flash
west 会调用 pyocd 下载。
这里需要说明以下,通过 pip 安装的 pyocd 并不能直接支持 G32R501,需要做一些修改,具体可参阅官方应用笔记《AN1126_G32R501 IDE与工具链使用说明》。源码仓库里我提供了一个简单的 patch 包,目录是:patch\pyocd_0.36.0。使用方法是这样的:在开发环境的命令行窗口,切换至 python 虚拟环境目录,我这里是 d:\zephyrproject.asset.venv\Lib\site-packages\pyocd。然后执行 patch 包目录下的 do_patch.bat 批处理文件。如下图:

请注意:d:\zephyrproject.asset.venv 和 d:\zephyrproject\g32r5_zephyr 分别是我开发环境中的 python 虚拟环境所在目录、移植目录的路径,需要修改为实际操作中的路径。如果是按照本文一路操作至此,这些目录路径就是正确的。
下面这个简短的视频,是在 vscode 里调试时的录像:在 vscode 里调试 Zephyr 程序-哔哩哔哩。
系统时钟移植¶
这一步,我们实现 Clock Control 驱动,完成系统时钟相关的功能。
先查看芯片手册中的时钟树:

MCU 的时钟源有:
- INTOSC1
- INTOSC2
- XTAL
系统时钟源有:
- PLL
- OSCCLK
SYSCLK 后级还有:
- APBCLK
- LSPCLK
这些内容,我们都准备写入时钟相关内容,涉及到的内容有:
- devicetree
- Kconfig
- C 源代码
- cmake
下面还是分步骤叙述。
系统时钟之 devicetree¶
在 dts\geehy\g32r5\g32r501.dtsi 文件中,soc 前增加:
clocks {
clk_intosc_1: clk-intosc-1 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <DT_FREQ_M(10)>;
status = "disabled";
};
clk_intosc_2: clk-intosc-2 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <DT_FREQ_M(10)>;
status = "disabled";
};
clk_xtal: clk-xtal {
#clock-cells = <0>;
compatible = "geehy,g32r5-xtal";
status = "disabled";
};
pll: pll {
#clock-cells = <0>;
compatible = "geehy,g32r5-pll-clock";
status = "disabled";
};
};
这里定义了 4 个系统时钟源:
- clk_intosc_1,clk_intosc_2 对应内部振荡器,频率都是 10MHz;
- clk_xtal 对应 XTAL;
- pll 对应系统锁相环。
soc 内增加:
soc {
sysclk: sysclk@50020800 {
compatible = "geehy,g32r5-sysclk";
#clock-cells = <0>;
reg = <0x50020800 0x200>;
};
// ...
};
也就是 SYSCLK 了。
上面每个 Node 对应的 compatible ,除了值为 fixed-clock 的都需要创建。
geehy,g32r5-sysclk¶
对应文件路径:dts\bindings\clock\geehy,g32r5-sysclk.yaml,内容:
description: G32R5 Sysclk
compatible: "geehy,g32r5-sysclk"
include: [clock-controller.yaml, base.yaml]
properties:
"#clock-cells":
const: 0
clocks:
required: true
clock-frequency:
required: true
type: int
description: |
default frequency in Hz for clock output
apb-prescaler:
type: int
required: true
enum:
- 1
- 2
lsp-prescaler:
type: int
required: true
enum:
- 1
- 2
- 4
- 6
- 8
- 10
- 12
- 14
include 部分可以理解为继承自 clock-controller 和 base ;clocks 是 SYSCLK 的时钟源;clock-frequency 是 SYSCLK 的频率;apb-prescaler 是 APB 分频系数;lsp-prescaler 是 LSP 分频系数。两个分频系数都是枚举类型,在相应的 dts/dtsi 文件里如果写了其他值会报错,这就保证了正确值的范围。
geehy,g32r5-pll-clock¶
对应文件路径:dts\bindings\clock\geehy,g32r5-pll-clock.yaml,内容:
description: |
G32R5 PLL Clock.
fPLLSYSCLK = fOSCCLK * (IMULT + FMULT) / (ODIV * PLLSYSCLKDIV)
compatible: "geehy,g32r5-pll-clock"
include: [clock-controller.yaml, base.yaml]
properties:
"#clock-cells":
const: 0
clocks:
required: true
imult:
type: int
required: true
description: SYSPLL Integer Multiplier, Range is [1, 128]
fmult:
type: int
required: true
description: |
SYSPLL Fractional Multiplier:
- 0: 0
- 1: 0.25
- 2: 0.5
- 3: 0.75
enum:
- 0
- 1
- 2
- 3
odiv:
type: int
required: true
description: SYSPLL Output Clock Divider
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
pllsysclkdiv:
type: int
required: true
default: 2
description: SYSCLK Divide Select, Range is [1, 128]
geehy,g32r5-xtal¶
这一部分内容不再赘述,可以查看代码仓库,路径:dts\bindings\clock\geehy,g32r5-xtal.yaml。
修改 g32r501_micro_evb.dts¶
在板子的 dts 文件里,增加时钟部分的内容,在 boards\geehy\g32r501_micro_evb\g32r501_micro_evb.dts 末尾追加内容下面的内容就可以:
&clk_intosc_1 {
status = "okay";
};
&pll {
imult = <24>;
fmult = <0>;
odiv = <1>;
pllsysclkdiv = <1>;
clocks = <&clk_intosc_1>;
status = "okay";
};
&sysclk {
status = "okay";
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(240)>;
apb-prescaler = <2>;
lsp-prescaler = <2>;
};
上述内容的作用:
- 启用 INTOSC1。
- 使能 PLL,时钟源 INTOSC1,倍频系数 24,分频系数 1,PLL输出频率 240 MHz。
- SYSCLK 时钟源 PLL,时钟频率 240 MHz, APB、LSP 分频系数都是 2,频率都是 120 MHz。
系统时钟之 Kconfig¶
修改 SYS_CLOCK_HW_CYCLES_PER_SEC¶
首先需要修改的是关于 SYS_CLOCK_HW_CYCLES_PER_SEC 的值。这个值是 SYSCLK,我们使用系统工具获取 dts 中 SYSCLK 的数值。修改文件:soc\geehy\g32r5\Kconfig.soc:
config SOC_FAMILY_G32R5
bool
config SOC_FAMILY
default "g32r5" if SOC_FAMILY_G32R5
config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default $(dt_node_int_prop_int,/soc/sysclk@50020800,clock-frequency) if SOC_FAMILY_G32R5
rsource "*/Kconfig.soc"
SYS_CLOCK_HW_CYCLES_PER_SEC 修改前是固定的数值 10000000,现在从 Devicetree 节点 /soc/sysclk@50020800 的 clock-frequency 直接获取。这样这个值就从 dts 传递到了 Devicetree,也不需要手工调整。
可选的 XCLKOUT 功能¶
此外,G32R501 还有外部时钟输出 功能,也就是 MCO ,我们通过 kconfig 实现这一可选功能。在文件 soc\geehy\g32r5\g32r501\Kconfig.soc 增加相应内容:
menuconfig XCLKOUT
bool "XCLKOUT"
if XCLKOUT
config XCLKOUT_PIN
int "XCLKOUT Pin"
default 16
help
16 GPIO16
18 GPIO18x2
config XCLKOUT_DIV
int "XCLKOUTDIV"
range 0 3
default 3
help
Selects the div value
0: /1
1: /2
2: /4
3: /8
choice XCLKOUT_SRC
prompt "XCLKOUT Source"
default XCLKOUT_SRC_INTOSC1
help
XCLKOUTSEL
config XCLKOUT_SRC_PLLSYSCLK
bool "PLLSYSCLK"
config XCLKOUT_SRC_PLLRAWCLK
bool "PLLRAWCLK"
config XCLKOUT_SRC_SYSCLK
bool "SYSCLK"
config XCLKOUT_SRC_APBCLK
bool "APBCLK"
config XCLKOUT_SRC_INTOSC1
bool "INTOSC1"
config XCLKOUT_SRC_INTOSC2
bool "INTOSC2"
config XCLKOUT_SRC_XTAL
bool "XTAL"
endchoice
endif
这一部分内容的效果是在 Kconfig 配置期间提供一个名为 XCLKOUT 的菜单项,以供配置XCLKOUT功能。还可以设置分频系数,输出 IO 管脚以及使用哪个时钟作为输出源。如下图这样:

可以把下面的内容追加到 boards\geehy\g32r501_micro_evb\g32r501_micro_evb_defconfig 就可以启用 XCLKOUT:GPIO16 输出 8 分频 的 SYSCLK,也就是 30 MHz。
CONFIG_XCLKOUT=y
CONFIG_XCLKOUT_DIV=3
CONFIG_XCLKOUT_SRC_SYSCLK=y
系统时钟之 C 源代码¶
相关的代码在 drivers\clock_control 文件夹,可以直接参考代码仓库。需要说明的是当前的驱动只实现了 get_rate 这个接口,其他功能尚未实现:
static DEVICE_API(clock_control, g32r5_clock_control_api) = {
.get_rate = clock_control_g32r5_get_rate,
};
系统时钟之 cmake¶
涉及两个文件:drivers\CMakeLists.txt 及 drivers\clock_control\CMakeLists.txt 具体内容就是把新增的 C 源码加入构建,具体内容请参考代码仓库。
到此,时钟相关驱动移植完成,可以编译下载。运行时,除了观察到 LED 灯亮1秒灭1秒地闪烁外,GPIO16 还能观测到频率为 30MHz 的时钟波形,这就是 XCLKOUT,也证明了 SYSCLK 确实是 240MHz。
这里有一个点需要提一下,编译时可能出现如下图的告警内容。出现这类告警的原因是 zephyr 源码目录下对应驱动目录没有源代码加入构建。这个是正常的,因为我们使用的是自己编写的代码,不在 zephyr 源码目录,这类告警可以忽略~

以上内容,可以参考代码仓库标题为增加 Clock 驱动 的提交。
IO 复用 - Pinctrl 驱动¶
Pinctrl 驱动负责处理 GPIO 复用。
相对于上一章节,改动的内容有:
- 新增的文件有:
- drivers/pinctrl/CMakeLists.txt
- drivers/pinctrl/pinctrl_g32r5.c
- dts/bindings/pinctrl/geehy,g32r5-pinctrl.yaml
- dts/geehy/g32r5/g32r501-pinctrl.dtsi
- include/dt-bindings/pinctrl/g32r501-pinctrl.h
- soc/geehy/g32r5/common/pinctrl_soc.h
- 修改的文件有:
- boards/geehy/g32r501_micro_evb/g32r501_micro_evb.dts
- drivers/CMakeLists.txt
这里不再赘述各个文件的内容,重点说明的是 zeohyr 中 Pinctrl 驱动的基本原理。 从芯片数据手册可以查阅到 IO 复用的相关信息,移植期间关注的是如何实现复用功能选择。这就要回到dts\geehy\g32r5\g32r501.dtsi 文件,这里展示了一部分内容。
pinctrl: pin-controller@40030000 {
compatible = "geehy,g32r5-pinctrl";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x40030000 0xC00>;
// ...
};
注意 compatible = "geehy,g32r5-pinctrl" 这一行,结合之前时钟系统移植的内容,我们需要入手的地方就是 geehy,g32r5-pinctrl.yaml 这个文件,路径 dts\bindings\pinctrl\geehy,g32r5-pinctrl.yaml:
compatible: "geehy,g32r5-pinctrl"
include: base.yaml
properties:
reg:
required: true
child-binding:
description: |
Base binding configuration for Geehy G32R5 MCUs
include:
- name: pincfg-node.yaml
property-allowlist:
- bias-disable
- bias-pull-up
- drive-push-pull
- drive-open-drain
- output-low
- output-high
properties:
pinmux:
required: true
type: int
description: |
Integer array, represents gpio pin number and mux setting.
These defines are calculated as: (pin_number<<4 | function<<0)
With:
- pin_number: The gpio pin number (0, 1, ...)
- function: The function number, can be:
* 0 : GPIO
* 1 : Alternate Function 1
* 2 : Alternate Function 2
* 3 : Alternate Function 3
* 4 : Alternate Function 4
* ...
这个文件描述了 G32R501 的 Pin Controller,其 child-binding (可以理解为子节点)继承于 pincfg-node,必须(要求)具备 pinmux 属性,这个属性被规定为 int 类型,值是 (pin_number<<4 | function<<0) 。这个表达式的内容也就是展示了 IO 复用的信息。
在 dts\geehy\g32r5\g32r501-pinctrl.dtsi 这个文件里,就包含了 G32R501 这颗芯片的全部 IO 复用信息,这里截取一部分展示:
#include <dt-bindings/pinctrl/g32r501-pinctrl.h>
&pinctrl {
/omit-if-no-ref/ pwm1_a_gpio0: pwm1_a_gpio0 {
pinmux = < G32R5_PINMUX(0, 1) >;
};
/omit-if-no-ref/ spia_ste_gpio0: spia_ste_gpio0 {
pinmux = < G32R5_PINMUX(0, 3) >;
};
// ...
};
上面截取的内容,是 GPIO0 复用为 pwm1_a 和 spia_ste 的记录。pinmux 的值都是宏表达式,结合 geehy,g32r5-pinctrl.yaml 文件的内容,可以知道两个记录分别对应复用编号 1 和 3 。
G32R5_PINMUX 这个宏是定义在 dt-bindings/pinctrl/g32r501-pinctrl.h 这个文件中。
前缀 /omit-if-no-ref/ 的意思是如果没有使用到这个 node 就不要把它加入最后整合的 dts 文件。
IO 复用还有一个很重要的文件 soc\geehy\g32r5\common\pinctrl_soc.h 这个文件主要定义了 IO 复用及配置信息,特别需要关注的是两个宏:
/**
* @brief Utility macro to initialize each pin.
*
* @param node_id Node identifier.
* @param prop Property name.
* @param idx Property entry index.
*/
#define Z_PINCTRL_STATE_PIN_INIT(node_id, prop, idx) { \
.pinmux = DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), pinmux), \
.cfg = ( \
(DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), bias_pull_up) << G32R5_PUPD_POS) | \
(DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), drive_open_drain) << G32R5_OTYPE_POS) \
),\
},
/**
* @brief Utility macro to initialize state pins contained in a given property.
*
* @param node_id Node identifier.
* @param prop Property name describing state pins.
*/
#define Z_PINCTRL_STATE_PINS_INIT(node_id, prop) \
{DT_FOREACH_PROP_ELEM(node_id, prop, Z_PINCTRL_STATE_PIN_INIT)}
这两个宏的作用是配合 dtc 工具,把 dts 文件里的列举的全部 IO 管脚信息转换为 C 源代码的内容,以便在源文件中使用。
理解上述内容其实挺费脑子的,我们暂时先这样做,待下一章节的内容中在结合实际使用再来试着理解这一部分内容。
UART 也要驱动起来¶
时钟、IO 复用都已经搞定,接下来 U(S)ART 就可以着手移植了。
在 zephyr 里 U(S)ART 对应 serial 。
这里也给出修改/新增文件的列表:
-
修改的文件有:
- Kconfig
- boards/geehy/g32r501_micro_evb/g32r501_micro_evb.dts
- boards/geehy/g32r501_micro_evb/g32r501_micro_evb_defconfig
- drivers/CMakeLists.txt
- dts/geehy/g32r5/g32r501.dtsi
-
新增的文件有:
- drivers/Kconfig
- drivers/serial/CMakeLists.txt
- drivers/serial/Kconfig
- drivers/serial/uart_g32r5.c
- dts/bindings/serial/geehy,g32r5-uart.yaml
经过前面的移植工作,对于新增/修改的内容也不再多做介绍。详细的变更内容可以从代码仓库查阅。这里说一下前一章节 IO 复用相关的内容。
首先看 boards/geehy/g32r501_micro_evb/g32r501_micro_evb.dts 里关于 UARTA 的相关内容:
&uarta {
status = "okay";
pinctrl-0 = <&uarta_tx_gpio29 &uarta_rx_gpio28>;
pinctrl-names = "default";
current-speed = <115200>;
};
其中 pinctrl-0 这一行表明使用 GPIO29,GPIO28 作为 UARTA_TX,UARTA_RX。uarta_tx_gpio29, uarta_rx_gpio28 在 dts\geehy\g32r5\g32r501-pinctrl.dtsi 中有定义:
// ...
/omit-if-no-ref/ uarta_rx_gpio28: uarta_rx_gpio28 {
pinmux = < G32R5_PINMUX(28, 1) >;
};
// ...
/omit-if-no-ref/ uarta_tx_gpio29: uarta_tx_gpio29 {
pinmux = < G32R5_PINMUX(29, 1) >;
};
// ...
我们再看一看构建目录下的 build\zephyr\zephyr.dts 文件,该文件是 dtc 工具合并整个项目所涉及到的全部 dts/dtsi 文件得到,是一个完整的 dts 文件。这里截取了一分部作为讲解用~
// ...
pinctrl: pin-controller@40030000 {
compatible = "geehy,g32r5-pinctrl";
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
reg = < 0x40030000 0xc00 >;
uarta_rx_gpio28: uarta_rx_gpio28 {
pinmux = < 0x1c1 >;
phandle = < 0x4 >;
};
uarta_tx_gpio29: uarta_tx_gpio29 {
pinmux = < 0x1d1 >;
phandle = < 0x3 >;
};
};
// ...
uarta: uart@50000c00 {
compatible = "geehy,g32r5-uart";
reg = < 0x50000c00 0x400 >;
interrupts = < 0x60 0x0 >;
status = "okay";
pinctrl-0 = < &uarta_tx_gpio29 &uarta_rx_gpio28 >;
pinctrl-names = "default";
current-speed = < 0x1c200 >;
};
// ...
可以看到 uarta_rx_gpio28,uarta_tx_gpio29 的 pinmux 属性都全被展开计算为整数,也就是前文起到的 (pin_number<<4 | function<<0) 这个表达式。
uarta_rx_gpio28,uarta_tx_gpio29 相关的这些内容,在源文件 uart_g32r5.c 中,通过宏定义 PINCTRL_DT_INST_DEFINE 被编译到程序中。
查看 .map 文件,可以找到类似这样的内容:
.rodata.__pinctrl_state_pins_0__device_dts_ord_22
0x08003e94 0x8 modules/hal_g32r5/drivers/serial/lib..__g32r5_zephyr__drivers__serial.a(uart_g32r5.c.obj)
pinctrl_state_pins_0__device_dts_ord_22 这个变量实际上其实是结构体数组,这一部分内容是 soc\geehy\g32r5\common\pinctrl_soc.h 里定义的:
typedef struct
{
uint16_t pinmux; /**< Pin configuration value. */
uint16_t cfg; /**< Output speed configuration value. */
} pinctrl_soc_pin_t;
#define Z_PINCTRL_STATE_PIN_INIT(node_id, prop, idx) { \
.pinmux = DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), pinmux), \
.cfg = ( \
(DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), bias_pull_up) << G32R5_PUPD_POS) | \
(DT_PROP(DT_PROP_BY_IDX(node_id, prop, idx), drive_open_drain) << G32R5_OTYPE_POS) \
),\
},
#define Z_PINCTRL_STATE_PINS_INIT(node_id, prop) \
{DT_FOREACH_PROP_ELEM(node_id, prop, Z_PINCTRL_STATE_PIN_INIT)}
uart_g32r5.c 里 PINCTRL_DT_INST_DEFINE 全部展开后就是这样了:
static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_22[] = {
{
.pinmux = ... ,
.cfg = ...,
},
{
.pinmux = ... ,
.cfg = ...,
},
};
在 uart_g32r5.c 的 g32r5_uart_init 会把这个结构体数组传递给 pinctrl_apply_state() 从而实现 IO 复用功能的设置。
static int g32r5_uart_init(const struct device *dev)
{
// ...
ret = pinctrl_apply_state(cfg->pinctrl, PINCTRL_STATE_DEFAULT);
if (ret < 0)
{
return ret;
}
// ...
}
移植介绍完成,现在编译下载,我们应该能看到:
- LED1 亮1秒灭1秒循环
- 串口打印 LED1 的状态
- GPIO16 输出 30MHz 的波形。
本章节的移植内容,请参阅代码仓库编号为 06b373a4706b9aa017bc8b625382d8f29b0515bd 的commit。
我也录制了一个简短的视频展示了上面的移植成果,请移步 B 站。
至此,移植 zephyr 到 G32R501 的介绍就完成了。
总结¶
这次的移植虽然只有三项功能,但是已经把移植 zephyr 的最基本操作介绍了一遍,移植的关键步骤也做了相关说明。
代码仓库的地址是:https://gitee.com/quincyzh/g32r5_zephyr 欢迎 Star~
希望这一份介绍能带给工程师朋友们一些帮助~也希望国产芯片越来越强,生态越来越旺!
【正文结束】