wangqy_ic 发表于 2025-7-15 17:59

来了来了 G32R501 可以用上 Zephyr 了 之二

本帖最后由 wangqy_ic 于 2025-7-17 15:44 编辑

# 移植-第二部分

## 先点个灯~

经过前面的步骤,新移植的框架已经搭建完成。下面我们就开始点灯~当然是通过 zephyr 的驱动程序去点灯。和前一部分类似,我会在下文按操作步骤写上编号,以便读者查阅。

- 1、dts 文件引入 GPIO
- 2、设备描述文件 geehy,g32r5-gpio.yaml
- 3、GPIO 驱动程序
- 4、修改以实现闪灯

### 1、dts 文件引入 GPIO 相关内容

修改移植目录下的 *dts\geehy\g32r5\g32r501.dtsi* 文件,加入 GPIO 相关内容:

```dts
/*
* 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
/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 为新增部分。这个时候,如果尝试进行编译,会出现类似下图中红色框的错误提示。这个错误出现的原因是,没有对应的*驱动程序描述文件*(嗯~这个名字是我胡乱起的,不知道是不是合适),为解决这个错误,请继续下一个步骤。

!(data/attachment/forum/202507/15/175250s9q96bvzv6w78sph.png "Pasted image 20250706165208.png")

### 2、设备描述文件 geehy,g32r5-gpio.yaml

在移植目录,创建这个文件 *dts\bindings\gpio\geehy,g32r5-gpio.yaml*,当然相应的目录也需要新建。文件内容:

```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* 的内容是:

```cmake
add_subdirectory_ifdef(CONFIG_GPIO gpio)
```

这里文件的有效内容就一行,也就是在 *CONFIG_GPIO* 生效的情况下,包含 *gpio* 目录到构建中~

在 *drivers\gpio* 下创建两个文件:*CMakeLists.txt* 和 *gpio_g32r5.c*。

*drivers\gpio\CMakeLists.txt* 文件的内容是:

```cmake
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* 文件就是驱动文件的具体实现了:

```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)* 下面分别说明。

#### DEVICE_API

这个宏展开后实质就是:

```C
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* 一行。

上述三步骤完成后,进行一次全新编译:

```shell
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* 批处理文件。如下图:

!(data/attachment/forum/202507/15/175405xhd575pz4yrqtzd4.png "Pasted image 20250706224409.png")

**请注意**:*d:\zephyrproject.asset\.venv* 和 *d:\zephyrproject\g32r5_zephyr* 分别是我开发环境中的 python 虚拟环境所在目录、移植目录的路径,需要修改为实际操作中的路径。如果是按照本文一路操作至此,这些目录路径就是正确的。

下面这个简短的视频,是在 vscode 里调试时的录像:

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=114806975631141&bvid=BV16N3BzXEbw&cid=30888820741&p=1&danmaku=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

## 系统时钟

这一步,我们实现 Clock Control 驱动,完成系统时钟相关的功能。

先查看芯片手册中的时钟树:

!(data/attachment/forum/202507/15/175440c0srrgjrvivfhw4b.png "Pasted image 20250715104648.png")

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*,内容:

```yaml

description: G32R5 Sysclk

compatible: "geehy,g32r5-sysclk"

include:

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*,内容:

```yaml
description: |
    G32R5 PLL Clock.

    fPLLSYSCLK = fOSCCLK * (IMULT + FMULT) / (ODIV * PLLSYSCLKDIV)

compatible: "geehy,g32r5-pll-clock"

include:

properties:
"#clock-cells":
    const: 0

clocks:
    required: true

imult:
    type: int
    required: true
    description: SYSPLL Integer Multiplier, Range is

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

```

结合前面的内容,这一部分也是比较容易理解:*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* 末尾追加内容下面的内容就可以:

```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*:

```Kconfig
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* 增加相应内容:

```Kconfig
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 管脚以及使用哪个时钟作为输出源。如下图这样:

!(data/attachment/forum/202507/15/175519s6q8wprw9o70reoy.png "Pasted image 20250715113149.png")

可以把下面的内容追加到 *boards\geehy\g32r501_micro_evb\g32r501_micro_evb_defconfig* 就可以启用 XCLKOUT:GPIO16 输出 8 分频 的 SYSCLK,也就是 30 MHz。

```Kconfig
CONFIG_XCLKOUT=y
CONFIG_XCLKOUT_DIV=3
CONFIG_XCLKOUT_SRC_SYSCLK=y
```

### 系统时钟之 C 源代码

相关的代码在 *drivers\clock_control* 文件夹,可以直接参考代码仓库。需要说明的是当前的驱动只实现了 get_rate 这个接口,其他功能尚未实现:

```C
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 源码目录,这类告警可以忽略~

!(data/attachment/forum/202507/15/175602zbu5hb7g9559zghy.png "Pasted image 20250715123210.png")

以上内容,可以参考代码仓库标题为*增加 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* 文件,这里展示了一部分内容。

```dts
                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*:

```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 复用信息,这里截取一部分展示:

```dts
#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 复用及配置信息,特别需要关注的是两个宏:

```C
/**
* @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 源代码的内容,以便在源文件中使用。

理解上述内容其实挺费脑子的,我们暂时先这样做,待下一章节的内容中在结合实际使用再来试着理解这一部分内容。

# 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 的相关内容:

```dts
&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* 中有定义:

```dts
        // ...

        /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 文件。这里截取了一分部作为讲解用~

```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* 里定义的:

```C
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 复用功能的设置。

```C
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。

我也录制了一个简短的视频展示了上面的移植成果:

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=114856552366170&bvid=BV1M6uEz3EvN&cid=31052795350&p=1&danmaku=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

至此,移植 zephyr 到 G32R501 的介绍就完成了。

# 总结

这次的移植虽然只有三项功能,但是已经把移植 zephyr 的最基本操作介绍了一遍,移植的关键步骤也做了相关说明。

代码仓库的地址是:https://gitee.com/quincyzh/g32r5_zephyr 欢迎 Star~

希望这一份介绍能带给工程师朋友们一些帮助~也希望国产芯片越来越强,生态越来越旺!

---

wangqy_ic 发表于 2025-7-15 18:04

@21小跑堂 #技术资源# #申请原创#

还有第一篇 https://bbs.21ic.com/icview-3467596-1-1.html

星云狂想曲 发表于 2025-7-15 18:16

我也挺想玩Zephyr的
羡慕一下楼主

wangqy_ic 发表于 2025-7-15 21:29

星云狂想曲 发表于 2025-7-15 18:16
我也挺想玩Zephyr的
羡慕一下楼主

赶紧学起来~

zephyr 源码里,当前支持的开发板有 788 款,市面上很多其他不支持的开发板,稍微修改 dts 就能支持~

pacer81 发表于 2025-7-16 12:48

弱弱的问一下,G32G051是哪家的MCU?

saibeistar 发表于 2025-7-16 13:23

强啊,赶紧学起来~

wangqy_ic 发表于 2025-7-17 15:48

pacer81 发表于 2025-7-16 12:48
弱弱的问一下,G32G051是哪家的MCU?

笔误笔误,抱歉哈~,更正一下是:G32R501

是极海新推出的针对实时控制应用的一款 MCU,基于Arm v8.1-M架构的Arm® Cortex®-M52内核

涡流远见者 发表于 2025-7-17 16:14

看起来Zephyr的入门门槛还是有一些的啊!
页: [1]
查看完整版本: 来了来了 G32R501 可以用上 Zephyr 了 之二