移植-第二部分
先点个灯~
经过前面的步骤,新移植的框架已经搭建完成。下面我们就开始点灯~当然是通过 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](home.php?mod=space&uid=2514928) {
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](home.php?mod=space&uid=288409) 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) 下面分别说明。
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 不再继续执行的~或者调试也可以判断。其他驱动程序也是怎样判断的。
4、修改以实现闪灯
前面的步骤,我们能正常编译程序。但是仍存在两个问题: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 里调试时的录像:
系统时钟
这一步,我们实现 Clock Control 驱动,完成系统时钟相关的功能。
先查看芯片手册中的时钟树:

MCU 的时钟源有:
系统时钟源有:
SYSCLK 后级还有:
这些内容,我们都准备写入时钟相关内容,涉及到的内容有:
- 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]
结合前面的内容,这一部分也是比较容易理解:imult,fmult,odiv 分别对应 PLL 的倍频系数、分频系数。
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](home.php?mod=space&uid=247401) 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 源代码的内容,以便在源文件中使用。
理解上述内容其实挺费脑子的,我们暂时先这样做,待下一章节的内容中在结合实际使用再来试着理解这一部分内容。
U(S)ART 也要驱动起来
时钟、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。
我也录制了一个简短的视频展示了上面的移植成果:
至此,移植 zephyr 到 G32R501 的介绍就完成了。
总结
这次的移植虽然只有三项功能,但是已经把移植 zephyr 的最基本操作介绍了一遍,移植的关键步骤也做了相关说明。
代码仓库的地址是:https://gitee.com/quincyzh/g32r5_zephyr 欢迎 Star~
希望这一份介绍能带给工程师朋友们一些帮助~也希望国产芯片越来越强,生态越来越旺!