打印
[G32R]

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

[复制链接]
354|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

移植-第二部分

先点个灯~

经过前面的步骤,新移植的框架已经搭建完成。下面我们就开始点灯~当然是通过 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 为新增部分。这个时候,如果尝试进行编译,会出现类似下图中红色框的错误提示。这个错误出现的原因是,没有对应的驱动程序描述文件(嗯~这个名字是我胡乱起的,不知道是不是合适),为解决这个错误,请继续下一个步骤。

Pastedimage20250706165208.png

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.txtgpio_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 批处理文件。如下图:

Pastedimage20250706224409.png

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

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

系统时钟

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

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

Pastedimage20250715104648.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_1clk_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]

结合前面的内容,这一部分也是比较容易理解:imultfmultodiv 分别对应 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@50020800clock-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 管脚以及使用哪个时钟作为输出源。如下图这样:

Pastedimage20250715113149.png

可以把下面的内容追加到 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.txtdrivers\clock_control\CMakeLists.txt 具体内容就是把新增的 C 源码加入构建,具体内容请参考代码仓库。

到此,时钟相关驱动移植完成,可以编译下载。运行时,除了观察到 LED 灯亮1秒灭1秒地闪烁外,GPIO16 还能观测到频率为 30MHz 的时钟波形,这就是 XCLKOUT,也证明了 SYSCLK 确实是 240MHz。

这里有一个点需要提一下,编译时可能出现如下图的告警内容。出现这类告警的原因是 zephyr 源码目录下对应驱动目录没有源代码加入构建。这个是正常的,因为我们使用的是自己编写的代码,不在 zephyr 源码目录,这类告警可以忽略~

Pastedimage20250715123210.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 文件,这里展示了一部分内容。

        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_aspia_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_gpio28dts\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_gpio28uarta_tx_gpio29pinmux 属性都全被展开计算为整数,也就是前文起到的 (pin_number<<4 | function<<0) 这个表达式。

uarta_rx_gpio28uarta_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.cPINCTRL_DT_INST_DEFINE 全部展开后就是这样了:

static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_22[] = {
    {
        .pinmux = ... ,
        .cfg = ...,
    },
    {
        .pinmux = ... ,
        .cfg = ...,
    },
};

uart_g32r5.cg32r5_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~

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


使用特权

评论回复
沙发
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 就能支持~

使用特权

评论回复
5
pacer81| | 2025-7-16 12:48 | 只看该作者
弱弱的问一下,G32G051是哪家的MCU?

使用特权

评论回复
6
saibeistar| | 2025-7-16 13:23 | 只看该作者
强啊,赶紧学起来~

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:感恩的心对人。

21

主题

112

帖子

4

粉丝