基于 GD32F4xx Soc 的 Zephyr 移植

 

如何在 GD32F450IK MCU 上移植 zephyr

如何在 GD32F450IK MCU 上移植 zephyr

Zephyr 应用程序开发

基本架构

  • 构建在app中进行

  • app基本构成

    <home>/app
    ├── CMakeLists.txt  // 构建系统的入口点
    ├── prj.conf
    └── src
        └── main.c
    
    • CMakeLists.txt 把Zephyr链接进app的构建中
    • prj.conf(Kconfig):指定内核配置
    • src:app 代码

创建应用程序

  1. 创建app工程

  2. 写CMakeLists.txt:Zephyr CMake Package

    cmake_minimum_required(VERSION 3.20.0)
       
    find_package(Zephyr)
    project(my_zephyr_app)
          
    target_sources(app PRIVATE src/main.c)
    

    把Zephyr内核作为一个cmake包拉入app

  3. 写Kconfig,见本节后面

  4. 设备树覆盖

重要的构建系统变量

  • 变量 BOARD, CONF_FILE, 和 DTC_OVERLAY_FILE

    • BOARD:支持的主板Supported BoardsBoard Porting Guide

    • Kconfig

      • CONF_FILE:Kconfig配置片段,多个文件,初始配置
      • OVERLAY_CONFIG:“混合”一些额外的config配置
    • DTS

    • ZEPHYR_MODULES:应用程序构建中使用的附加目录的绝对路径

    • 在CMakeLists.txt中定义 set(BOARD, gd32f450z_eval)

      • 作为shell的环境变量

      • 作为命令行参数

        west build -b gd32f450z_eval
        cmake -Dgd32f450z_eval
        

应用程序 CMakeLists.txt

  • 在新行上添加您的应用程序的板配置的名称

    set(BOARD qemu_x86)
    
    • Zephyr是如何确定BOARD参数的?
      • 首先看CMake cache
      • cmake命令行-DBOARD=YOUR_BOARD
      • west命令行 -b your_board
      • 环境变量 ${BOARD}
      • app的CMakeLists.txt中 set(BOARD 你的主板)
  • 确定配置文件

    • 一般是proj.conf,这是项目配置文件,Kconfig是内核配置

    • 否则用CONF_FILE变量,可以在CMakeLists.txt中设置

      set(CONF_FILE "conf_file1.conf")
      list(APPEND CONF_FILE "conf_file2.conf")
      

      参见 初始配置

  • 确定设备树覆盖层,要配置DTC_OVERLAY_FILE

  • 如果要配置内核,则在CMakeLists.txt同级创建Kconfig,这样Zephyr构建系统可以检测到Kconfig文件(也可以设置KCONFIG_ROOT改变路径),参见《配置系统

    # SPDX-License-Identifier: Apache-2.0
         
    mainmenu "Your Application Name"
         
    # Your application configuration options go here
         
    # Sources Kconfig.zephyr in the Zephyr root directory.
    #
    # Note: All 'source' statements work relative to the Zephyr root directory (due
    # to the $srctree environment variable being set to $ZEPHYR_BASE). If you want
    # to 'source' relative to the current Kconfig file instead, use 'rsource' (or a
    # path relative to the Zephyr root).
    # 所有的'source'语句都是相对于Zephyr根目录工作的(由于$srctree环境变量被设置为$ZEPHYR_BASE)。
    # 如果你想要'source'相对于当前Kconfig文件,使用'rsource'(或相对于Zephyr根目录的路径)。
    source "Kconfig.zephyr"
    
  • 完成上面步骤后再加上

    find_package(Zephyr)
    project(my_zephyr_app)
    

    find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})是强制使用环境变量的ZEPHYR_BASE

应用程序配置

应用程序配置目录

  • 除了使用手动指定的CONF_FILE, OVERLAY_CONFIG, 和 DTC_OVERLAY_FILE,Zephyr构建系统还可以用APPLICATION_CONFIG_DIR变量来指定配置目录

    • 在Cmake命令行指定:-DAPPLICATION_CONFIG_DIR=<path>

    • 在CMakeLists.txt中指定

      set(application_config_dir <path>)
      
    • 默认是app的源码目录的 prj.conf

      CONFIG_CPLUSPLUS=y
      
      # 启用实验特性告警
      CONFIG_WARN_EXPERIMENTAL=y
      

      这是应用程序的配置,可以参数样例

Kconfig 配置

引入第三方库

引入的第三方库的编译选项要和Zephyr相同,所以 Zephyr 给了一些函数来得到 Zephyr 构建系统的编译选项

构建应用程序

  • west

      west build -b reel_board samples/hello_world
    
  • west & make

      # 只用一次make
      west build -b reel_board samples/hello_world -- -G"Unix Makfiles"
    
      # 以后都用 make
      west config build.generator "Unix Makefiles"
      west build -b reel_board samples/hello_world
    
  • CMake & ninja

      # 用 cmake 配置 ninja 构建系统
      cmake -Bbuild -GNinja -DBOARD=reel_board samples/hello_world
    
      # 用 ninja 生成构建系统
      ninja -Cbuild
    
  • cmake & make

      # 用 cmake 配置 make 构建系统
      cmake -Bbuild -DBOARD=reel_board samples/hello_world
    
      # 用 make
      make -Cbuild
    

构建的更多方法

  • 命令行指定CONF_FILE

    • west

        west build -b <board> -- -DCONF_FILE=prj.1.conf prj.2.conf ...
      
    • cmake & ninja

        cmake -Bbuild -DBOARD=reel_board samples/hello_world -DCONF_FILE=prj.alternate.conf ..	
      
        ninja -Cbuild
      
  • CMakeLists.txt中创建BOARD, CONF_FILE变量

构建目录内容

  • <home>/app/build 是 west/cmake 创建出的目录,其中:
    • build/zephyr 是构建文件的输出目录
    • build/zephyr/.config 是构建app的配置文件
    • zephyr.elf是最终输出文件

rebuild 应用程序

  • 清除文件

      west build -t clean
    
      ninja clean
    
  • 重新编译

      west build -t pristine
    
      ninja pristine
    

运行应用程序

  • 下载到主板

      west flash
    
      ninja flash
    
  • 下载到模拟器 QEMU

    • 编译为QEMU-x86/qemu-cortex-m3

        west build -b qemu_x86 samples/hello_world
        west build -b qemu_cortex_m3 samples/hello_world
      
    • 在模拟器中运行,在<home>/app/build中运行

        west build -t run
      
        ninja -run
      

    其他版本的QEMU,要把QEMU_BIN_PATH设置为你安装的QEMU路径 可以在目标target后加_<模拟器>指定:west build -t run_qemu

应用程序调试

  • 用 GNU GDB + QEMU 在系统中设置的本地 GDB 服务器,这个服务器的端口是1234
  • QEMU服务器启动,侦听gdb连接

      qemu -s -S <image>
    
    • -S(Stop):不启动CPU,而是用户输入c
    • -s(shorthand):是-gdb tcp::1234的简写
  • .gdbinit是gdb初始化脚本

      # 连接到端口1234上的远程 GDB 服务器
      target remote localhost:1234
      dir ZEPHYR_BASE
    
  • gdb 图形界面

      .../path/to/gdb --tui zephyr.elf
    

west 工具

west 基本命令

  • init

      west init -m https://github.com/zephyrproject-rtos/zephyr zephyrproject
    
    • 把仓库克隆到zephyrproject/目录下
    • 把west的topdir设置为zephyrproject
    • 把 manifest.file 设定为 west.yml
  • update

      west update
    

    west 使用 manifest.file 来决定丢失的项目应该放在工作区的哪个位置,从哪些 url 中克隆它们,以及哪些 Git 修订应该在本地签出

  • build

      west build -b <board>
    
      # 强制重新cmake
      west build -b <board> --cmake
      west build -b <board> -c
    
    • build/目录中实现构建配置
    • 下次直接 west build 即可
      # 指定 app 源码目录
      west build -b <board> path/to/source/dir
    
  • config

    配置west选项

      # 配置 west build 为 reel_board 构建
      west config build.board reel_board
    
      # 配置 west,指定build生成目录
      # 这样 west build -b reel_board samples/hello_world
      # 将使用 build/reel_board/hello_world目录为生成目录
      west config build.dir-fmt "build/{board}/{app}"
    
      # 配置 west 总是重新构建
      west config build.pristine always
    
      # 配置 cmake 选项,总是启用 CMAKE_EXPORT_COMPILE_COMMANDS
      west config build.cmake-args -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
    
      # 启用 CMAKE_VERBOSE_MAKEFILE,以便 CMAKE 总是生成一个详细的构建系统
      west config build.cmake-args -- -DCMAKE_VERBOSE_MAKEFILE=ON
    
      # 同时启用 CMAKE_EXPORT_COMPILE_COMMANDS 和 CMAKE_VERBOSE_MAKEFILE
      west config build.cmake-args -- "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_VERBOSE_MAKEFILE=ON"
    
  • target

    设定生成目标

      # 为qemu_x86 主板构建并运行 Hello World 示例
      west build -b reel_board -t run samples/hello_world
    
      # 删除build目录和west配置
      west build -t pristine
    
      # 列出所有目标
      west build -t help
    
  • pristine

    原始版本

      # 总是重新构建,相当于-p=always
      west build -p -b reel_board samples/hello_world
    
      # 自动检测是否重新构建
      west build -p=auto -b reel_board samples/hello_world
    
  • verbose

    详细构建

      west -v build -b reel_board samples/hello_world
    
  • --为cmake提供参数

      # 使用make构建而不用ninja
      west build -b reel_board -- -G'Unix Makefiles'
    
      # 将 CMAKE_VERBOSE_MAKEFILE 设置为 ON
      west build -b reel_board -- -G'Unix Makefiles' -DCMAKE_VERBOSE_MAKEFILE=ON
    
      # 将 DTC_OVERLAY_FILE 设置为 enable-modem.overlay
      west build -b reel_board -- -DDTC_OVERLAY_FILE=enable-modem.overlay
    
      # 将 file.conf Kconfig 片段合并到构建的.config 中
      west build -- -DOVERLAY_CONFIG=file.conf
    
  • -o为ninja或make提供参数

      # 传递-dexplain 给 ninja
      west build -o=dexplain
    
      # 传递 –keep-going 给 make
      west build -o=--keep-going
    
      # 并行构建
      west build -o=-j4
    

west flash

  • 烧录版本,相当于make flashninja flash
  • 指定搜索的目录

      west flash --build-dir path/to/build/directory
    
    • west 在这构建目录中搜索zephyr.elf
    • 默认是在build/搜索
  • 指定下载器

      west flash -r jlink
    
    • 可以在构建时指定下载器

        # 给cmake提供BOARD_FLASH_RUNNER变量
        west build [...] -- -DBOARD_FLASH_RUNNER=jlink
      
    • 每个调试器都有自己的选项,可以先查看不同的调试器支持什么选项

        west flash --context
      

      或者重新构建系统,以便更新调试器支持的选项

        west flash -H
      
        # 只看jlink
        west flash -H -r jlink
      

Debug 调试

  • 挂接debugger到主板程序,并打开调试器的控制台

      west debug
    
      # 指定 jlink
      west debug -r jlink
    
  • 挂接debugger到主板程序,并打开端口,以便IDE可以通过它连接到debugger

      west debugserver
    
      west debugserver -r jlink
    
  • 查看west支持的调试器选项

      # 不编译
      west debug --context
    
      # 编译后显示 
      west debug -H
      west debug -H -r jlink
    

CMake 构建系统

  • cmake分成两步
    • 配置阶段:执行CMakeLists.txt脚本,得到 Zephyr 构建模型
    • 构建阶段:利用make或ninja编译
    • 以后的编译,就直接使用build/构建模型,不需要再cmake了

配置阶段

  • 和配置阶段有关的输入文件是.dts, .dtsi文件,binding.yaml文件和Kconfig文件
  • Zephyr首先把.dts, .dtsi, overlay + binding.yaml -> zephyr.dts
  • 然后,zephyr.dts + Kconfig(prj.conf) -> autoconf.h, build/zephyr/.config
  • zephyr.dts -> devicetree.h,让C可以获得设备树的数据
  • 所以配置阶段的产出是
    • zephyr.dts
    • autoconf.h
    • devicetree.h

构建阶段

生成zephyr.elf/bin/hex

配置系统(Kconfig)

  • app可以配置zephyr的内核和子系统来支持自己的功能
  • Kconfig的输出是autoconf.h

交互式 Kconfig 界面

  • 修改 Kconfig 的方式
    • menuconfig
    • guiconfig
    • 手动编译zephyr/.config
  • 修改命令

      # west
      west build -t menuconfig
    
      # ninja
      ninja menuconfig
    

设置 Kconfig 配置值

可见与不可见符号

  • prompt定义的符号是可见符号

      config FPU
          bool "Support floating point operations"
          depends on HAS_FPU
    
  • 不可见符号是从其他符号取值或者默认值

在配置文件中设置符号

  • 符号的默认值来自主板的*_defconfigapp/prj.con的合并,赋值语法

      CONFIG_<符号名>=<符号值>
    
      # bool 值设置为 Y
      CONFIG_<符号名>=y
      # bool 设置为 N,就是注释掉即可 
      # CONFIG_SOME_OTHER_BOOL
    
  • 符号的初始值来自多个文件的合并
    • zephyr/<arch>/<board>/<board>_defconfig
    • CMake cache中的CONFIG_xxx的项
    • app的app/pro.conf, CONF_FILE
  • 对于不可见符号的赋值,修改zephyr/.config是没用的
    • 必须修改boards/<arch>/<board>/Kconfig.defconfig,它才能决定不可见符号的值
    • Kconfig.defconfig对一个符号的使用会出现在多个地方

      ```cmake
      # 先声明一个符号,也称符号的基本定义
      config FOO_WIDTH
          int
      
      # 在后面的某处再赋值
      if BOARD_MY_BOARD
          
      config FOO_WIDTH
          default 32 # Kconfig使用第一个default作为默认值
          
      endif
      ``` - 之所以用 `Kconfig.defconfig` 是避免guiconfig会修改固定的参数 - 因为`<board>_defconfig`中的符号是可配的,如果把所有符号都写在里面,那么对于一些主板不希望改变的符号也可能被意外修改
      

配置 choices 单选项

  • choices类似枚举,choices中一个元素为Y,则其他元素为N

Kconfig-技巧和最佳实践

项目可以用Kconfig语法在pro.conf中自定义一些符号

select 选择语句

selcet:一个符号为Y时,通过select挑选另一个符号也为Y

config CONSOLE
    bool "控制台支持"

...

config USB_CONSOLE
    bool "USB 控制台支持"
    select CONSOLE  # 挑选CONSOLE也为Y

但select语法的副作用比较大,不建议用

depends on

config CONSOLE
    bool "控制台支持"
    default Y

...

config USB_CONSOLE
    bool "USB 控制台支持"
    depends on CONSOLE  # 依赖于CONSOLE为Y
  • 不启用CONSOLE则要显示指定CONFIG_CONSOLE=n

将设备树信息导入Kconfig的函数

config FLASH_BASE_ADDRESS
     default $(dt_node_reg_addr_hex,/soc/spi@1001400,1)

Kconfig扩展

  • Zephyr使用的Kconfig,可以引入环境变量,通过$(FOO)
  • 包含其他Kconfig:
    • source "foo/bar/*/Kconfig,如果没匹配到会引发错误
    • osource "foo/*/Kconfig,没匹配到则不操作no-op
    • rsource "qaz/Konfig相对于这个文件的路径
    • orsource "qaz/Konfig相对于这个文件的路径,不匹配无错误

设备树

  • 两类设备树文件
    • device tree sources:描述设备树结构
    • device tree bindings:描述设备内容和数据类型
    • 最终生成devicetree.h
  • 属性
    • compatible:节点硬件设备名,具体的名字可以在dts/bindings/vendor-prefixes.txt中找
      • 当硬件是一个通用设备时,也可以不指定厂商,而是如gpio-keys, mmio-sram这类名称
      • Zephyr构建系统通过compatible找设备的正确bindings.yaml
      • 驱动程序通过devicetree.h的API通过compatible找硬件节点的信息
    • label
      • 通过device_get_binding()找到设备对象device

输入和输出文件

  • 输入文件
    • 对于设备树,有4种类型的文件
    • dts
    • dtsi
    • overlays(.overlay)
    • bindings(.yaml)
    • 每个主板都应该有一个描述主板硬件的<board>.dts
    • 比如reel_board有boards/arm/reel_board/reel_board.dts
    • dts会包含dtsi文件,它主要是描述不变动的CPU和SoC
    • dts/common/skeleton.dtsi这个特殊的文件,描述了一个完备的最小系统设备树
    • <board>.dts通过overlays文件来扩展,它是因为不同的目的来修改基础设备
    • overlay文件是在最后合并,以便覆盖<board>.dts
    • bindings.yaml描述设备节点的内容
    • dts/bindings/目录包含了bindings文件
    • 它让Zephyr通过gen_defines.py从devicetree和bindings中生成驱动和c宏
  • 输出文件
    • devicetree.h
    • zephyr.dts

所有硬件信息的单一来源

  • 硬件信息只能从设备树文件中得到
  • 驱动应该用devicetree API来实例化硬件设备
  • 应用程序应该用设备别名alias来引用设备device
  • <board>.yaml是给 zephyr 的 Test 用的
  • 驱动通过Kconfig文件来确定启用哪些compatible硬件,我们应该通过overlay来修改

设备树绑定

  • Zephyr在配置阶段时,会将devicetree中的节点和绑定文件匹配,验证节点信息是不是满足绑定文件的要求
    • 这是一个设备树的节点

        bar-device {
            compatible = "foo-company,bar-device"
            num-foos = <3>
        }
      
    • 这是节点对应的绑定文件

        compatible = "foo-company,bar-device"
      
        properties: 
            num-foos:
                type: int
                required: true
      
    • Zephyr的构建系统用绑定文件 - 验证devicetree节点信息是不是满足绑定文件的要求 - 把设备参数转换为devicetree.h头文件 - 为bar-device节点中的num-foos属性生成一个宏 - 让这个属性扩展为3 - 在c语言中,可以用devicetree.h中的api得到这个值

      • 如果设备树的节点没有compatible,它的compatible继承它的父节点

绑定文件在哪?

  • 绑定文件和它的compatible同名
    • 比如节点的`compatible = “foo-company,bar-device”
    • 它对应的绑定文件名是foo-company,bar-device.yaml
    • 保存的路径在下面目录的dts/bindings/目录中
      • zephyr/
      • app/
        • 比如app/dts/bindings/foo-company,bar-device.yaml
      • <board>/
      • CMake变量DTS_ROOT的路径

绑定文件的语法

  • description: 说明
  • compatible: 要绑定的设备树节点

properties

  • 这个binding的节点需要满足的属性的要求和描述

      compatible: "manufacturer,serial"
    
      properties: 
          reg: 
              type: array				# 说明怎么生成devicetree.h文件中的宏的格式
              description: UART 外设	 # 给人读的,说明这个变量的意思
              required: true			# 说明在devicetree文件中是不是该出现
              default: 3   			# 属性的默认值:foo = <3>,与required:false结合才有意义
    
    • 这样zephyr就知道这个节点有哪些属性
    • 这些属性是不是该出现
    • 为属性生成怎样格式的数据
    • 使用default值有风险,对特定主板可能不正确

      child-binding

  • 您可以使用这个键,约束与这个绑定匹配的节点的子节点,子节点也要满足的属性要求

    • 设备树节点有多个子节点,子节点都有相同的属性,这时绑定可以用子绑定来描述
    • 有子节点的设备树节点

        pwmleds {
            compatible = "pwm-leds";  // 驱动属性
      
            red_pwm_led { pwms = <&pwm3 4 15625000>;};
            green_pwm_led {pwms = <&pwm3 0 15625000>;};
        }
      
    • 对应的绑定文件

        compatible:  "pwm-leds"
      		
        child-building:
            description: LED that uses PWM
      
            properties:
                pwms:
                    type: pandle-array
                    required: true
      

bus

  • 节点是一个总线控制器,使用 bus 说明是哪个,如spi
    • 绑定文件通过这个key通知zephyr构建系统,和这个绑定compatile匹配的设备树节点的子节点都在这个总线上

      on-bus

  • 如果设备节点是总线上的设备,说明是哪个总线,如spi
    • zephyr构建系统拿到一个设备树接点,会通过compatible从绑定文件中查找对应节点的绑定
    • 相同的compatible节点可以因为on-bus的不同而匹配不同的绑定文件
    • 比如节点

        spi-bus@0 {
            sensor@0 { compatible = "厂商,型号"; reg = <0>;
            };
        };
        i2c-bus@0 {
            sensor@79 {compatible = "厂商,型号"; reg = <79>;};
        };
      
    • 可以因on-bus的不同,有不同的绑定文件spi.yaml和i2c.yaml

      下面是绑定文件: 厂商,型号-spi.yaml

        compatible: "厂商,型号"
        on-bus: spi
      

      下面是绑定文件: 厂商,型号-i2c.yaml

        compatible: "厂商,型号"
        on-bus: i2c
        properties:
            use-clock-stretching:
                type: bool
                required: true
      

foo-cells

  • foo域的信元(cell)说明符
    • 设备树节点

        my-device { 
            pwms = <&pwm0 1 2>, <&pwm3 4>; 
        };
      
      • pwms是一个节点数组phandle-array,这个一个pwm域
      • zehpyr构建工具会将节点的pwms转换为pwm,去找pwm域的信元说明符
      • zephyr构建工具会找到pwm0和pwm3节点

          pwm0: pwm@0 { compatible = "foo,pwm"; #pwm-cells = <2>; 
          };
        
          pwm3: pwm@3 { compatible = "bar,pwm"; #pwm-cells = <1>;
          };
        
      • 说明符
        • 从节点中找到#pwm-cells属性(即pwm信元数量属性)
        • 属性值的 &pwm0 1 2 有两个信元,1和2,匹配 #pwm-cells = <2>;
        • 这些信元被认为是与 phandle 数组中的 pwm0 相关联的说明符
        • 信元4是pwm3的说明符
    • 绑定文件
      • 每个pwm控制器的绑定必须有一个*-cells键名,用来给节点的信元说明符命名
      • 对于foo,pwm.yaml文件

          compatible: "foo,pwm"
          ...
          pwm-cells: # pwm域的信元名称
              - channel
              - period
        
      • 对于bar,pwm.yaml文件

          compatible: "bar,pwm"
        
          pwm-cells:
              - period
        
    • 节点也可以有*-names属性,直接给信元名称。
      • 我们可以通过DT_PWMS_CHANNEL_BY_NAME这种api直接读取信元说明符的值
    • 还可以通过specifier-space直接指定*-names(*域的名称)和*-cells(*域的信元)属性名称

*-gpios

  • *-gpios*域的gpio属性)
    • foo-gpios解析为#gpios-cells即:gpios的信元

include

  • 绑定文件可以包含其他绑定文件
  • 自己写绑定文件时,先要看要绑定的属性有没有在base.yaml文件中
  • 包含多个文件include: [a.yaml, b.yaml]
  • 只包含文件中的一些属性

      include:
          - name: a.yaml
          property-allowlist:
              - 需要的属性1
              - 需要的属性2
          - name: b.yaml # 部分包含,必须有name键
          property-blocklist:
              - 不需要的属性1
              - 不需要的属性2
          - c.yaml # 全包含,可以没有name键
    

interrupts 中断属性

  • 关于设备产生的中断的信息,被编码为一个或多个中断说明符的数组。每个中断指定符都有一定数量的信元
  • 更多细节请参见Devicetree Specification release v0.3中的2.4节,中断和中断映射
  • Zephyr的设备树绑定语言允许您在中断说明符中为每个信元指定一个名称

绑定文件的一般规则

  • compatible 值为 vnd,foo 的绑定必须名为 vnd,foo.yaml
  • 绑定是特定于总线的,则加bus名称:vnd,foo-i2c.yaml
  • 绑定的厂商名称在:dts/bindings/vendor-prefixes.txt

zephyr推断绑定

  • /zephyr,user推断绑定是构建系统自动创建的绑定
    • 根据其在最终设备树中的属性值动态创建的绑定
  • 存在的原因是当我们只需要一些简单属性时,提供的一个使得的容器
  • 对于下面的属性,我们放到 /zephyr,user节点中

    ```cmake /{ zephyr,user { boolean; bytes = [81 82 83];

          handle = <&gpio0>;
          handles = <&gpio0>, <&gpio1>;
    
          signal-gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
      };   };
    
  • 我们可以用API直接得到对应属性的值

      #define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
    
      DT_PROP(ZEPHYR_USER_NODE, boolean)	// 1
      DT_PROP(ZEPHYR_USER_NODE, bytes) 	// {0x81, 0x82, 0x83}
    
  • 我们还可以从handle属性得到节点对应的设备对象

      // dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));
      struct device *dev = DEVICE_DT_GET(DT_PROP(ZEPHYR_USER_NODE, handle));
    
  • 从handles属性一次得到多个设备对象

      /* 
       * struct device *devs[] = {
       *		DEVICE_DT_GET(DT_NODELABEL(gpio0)),
       *		DEVICE_DT_GET(DT_NODELABEL(gpio1))
       *	};
      */
    
      #define PHANDLE_TO_DEVICE(node_id, prop, idx) \
                  DEVICE_DT_GET(DT_PHANDEL_BY_IDX(node_id, prop, idx))	
    
      struct device *devs[] = {
          DT_FOREACH_PROP_ELEM(ZEPHYR_USER_NODE, handles, PHANDEL_TO_DEVICE)
      };
    
  • 可以把 signal-gpios 定义的引脚转换为 struct gpio_dt_spec

      #include <zephyr/drivers/gpio.h>
    
      #define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
    
      const struct gpio_dt_spec signal = GPIO_DT_SPEC_GET(ZEPHYR_USER_NODE, signal_gpios);
    
      gpio_pin_config_dt(&signal, GPIO_OUTPUT_INACTIVE);
      gpio_pin_set_dt(&signal, 1);
    

    可以在 gpio_dt_spec, GPIO_DT_SPEC_GET), gpio_pin_configure_dt() 找到相关的API,也就是我需要学习这些API来写驱动和应用程序

从C/C++访问设备

访问设备的API都是来自devicetree.h这个文件

节点标识符(节点ID,node_id)

  • node_id不是值
    • 不能存在变量里 void *i2c0 = DT_INST(1, vnd_soc_i2c);
    • 只能用宏定义指代:#define MY_I2C DT_NODELABEL(i2c1)
/dts-v1/;

/ {
	aliases { sensor-controller = &i2c1; };

	soc {
		i2c1: i2c@40002000 {
			compatible = "vnd,soc-i2c";
			label = "I2C_1";
			reg = <0x40002000 0x1000>;
			status = "okay";
			clock-frequency = < 100000 >;
		};
	};
};
  • node_id指向C语言形式的设备树节点
  • 得到node_id的方法
    • DT_PATH(…设备树节点的完整路径)
      • DT_PATH(soc, i2c_40002000)拿到C语言形式的节点
    • DT_NODELABEL(节点标签名)
      • 节点的标签名可以在SoC.dtsi文件中找到,所以我们需要先看看用到的 SoC 设备树的文件内容
      • DT_NODELABEL(i2c1)
    • DT_ALIAS(节点别名)
      • 节点别名就是/alias节点包含的属性名
      • DT_ALIAS(sensor-controller)
    • DT_CHOSEN(被选节点)
      • 节点名是/chosen节点包含的属性名
    • DT_INST(实例号)
      • 由驱动程序完成,实例号是基于匹配compatible来引用单个节点的方法
      • DT_INST(x, vnd_soc_i2c)
    • DT_PARENT(node_id)/DT_CHILD(node_id)
      • 拿到当前节点的父/子节点的引用

读写节点中的属性

  • 检查节点有没有属性

      DT_NODE_HAS_PROP(MY_I2C, clock_freqency)
    
  • 读节点属性的值

      DT_PROP(node_id, property)
    
      DT_PROP(DT_PATH(soc, i2c_40002000), clock_freqency) 	// 100_0000
      DT_PROP(DT_NODELABEL(i2c1), clock_freqency) 			// 100_0000
      DT_PROP(DT_NODELABEL(i2c1), status) 					// "okay"
    
  • 对于节点中数组形式的数据:

      xxx: xxx@1234 {
          a = <1000 2000 3000>;  	// data-array
          b = [aa bb cc dd];		// uint8-array
          c = "aaa", "bbb";		// str-array
      };
    
    • 读取的结果:

        int a[] = DT_PROP(DT_NODELABEL(xxx), a);	// {0x1000, 0x2000, 0x3000}
        uchar b[] = DT_PROP(DT_NODELABEL(xxx), b);	// {0xaa, 0xbb, 0xcc}
        char *c[] = DT_PROP(DT_NODELABEL(xxx), c);	// {"aaa", "bbb"}
      
    • 得到设备树属性为数组的元素个数

        size_t a_len = DT_PROP_LEN(DT_NODELABEL(xxx), a);	// 3
      

      读取寄存器属性

  • DT_NUM_REGS(node_id):节点的reg属性中,寄存器块总数
  • 读寄存器的长度和大小
    • 节点只有一个寄存器块的时候
    • 多个寄存器块
      • DT_REG_ADDR_BY_IDX(node_id, idx):idx处寄存器块的地址
      • DT_REG_SIZE_BY_IDX(node_id, idx):idx处块的大小
    • idx是一个常量,它是在编译期使用的

中断属性

  • 节点中断说明符的总数
    • DT_NUM_IRQS(node_id)
  • 读中断节点handle数组的第idx个元素的val信元的值
    • DT_IRQ_BY_IDX(node_id, idx, val)
    • val:中断说明符中信元的名称
    • 使用这个宏,需要查找节点的bindings文件来找到val的名称
    • 比如读定时器0的中断优先级

        #define ARM_TIMER_NODE DT_INST(0, arm_armv8_timer)
      
        DT_IRQ_BY_IDX(ARM_TIMER_NODE, 0, priority)
      
  • 读中断节点的中断编号
    • DT_IRQN(node_id)
  • 目前还没有很好的文档说明,如果要编写设备驱动程序,需要阅读脚本的源代码和现有驱动程序以获得更多细节

phandle 属性

其他 API

写设备驱动的惯例

  • 有专有宏来写设备驱动
    • 首先,把DT_DRV_COMPAT定义为驱动支持的compatbile值,这个值就是传递给 DT_INST() 的值

        #include <zephyr/devicetree.h>
      
        #define DT_DRV_COMPAT my_driver_compat
      
        // = DT_INST(0, my_driver_compat)
        DT_DRV_INST(0)
      
        // = DT_PROP(DT_INST(0, my_driver_compat), clock_frequency)
        DT_INST_PROP(0, clock_frequency)
      
    • 基于实例的 API

  • 特定硬件的 API

生成的宏

  • 我们devicetree.h中的API,它的数据又来自应用程序的 build/ 目录下的 devicetree_unfixed.h,它里面是各种带有设备树数据的宏

设备树的工作原理

获取您的设备树并生成 header

  • 主板的设备树<board>.dts先要包含dts/<arch>/<vendor>/<soc>.dtsi,最终生成 build/zephyr.dts
    • 我们可以用下面的命令来看看构建主板的基本设备树的过程,和它保存的文件在哪

        west build -b qemu_cortex_m3 -s samples/hello_world --cmake-only
      

从设备树节点获取struct device

  • 写app时,我们需要获得和设备树节点对应的驱动级 struct device
    • 设备树

        /{
            soc { 
                serial0: serial@40002000 {status = "okay"; current-speed=<115200>;};
            }
            aliases { my-serial = &serial0; };
        	chosen { zephyr,console = &serial0; };
        };
      
    • app:想要串口的 struct device

      • 造一个node_id的宏

          /* Option 1: by node label */
          #define MY_SERIAL DT_NODELABEL(serial0)
          /* Option 2: by alias */
          #define MY_SERIAL DT_ALIAS(my_serial)
          /* Option 3: by chosen node */
          #define MY_SERIAL DT_CHOSEN(zephyr_console)
          /* Option 4: by path */
          #define MY_SERIAL DT_PATH(soc, serial_40002000)
        
      • 通过 node_id 获得设备

          // 通过 DEVICE_DT_GET(node_id)
          const struct device *uart_dev = DEVICE_DT_GET(MY_SERIAL);
        
          if (!device_is_ready(uart_dev)) { return -ENODEV; }
        
        • 可能遇到的问题
          • 这个设备没有启用,我们可以这样避免这种问题

              #if DT_NODE_HAS_STATUS(MY_SERIAL, okay)
              const struct device *uart_dev = DEVICE_DT_DET(MY_SERIAL);
              #else
              #error "Node is disable"
              #endif
            
          • 代码可以编译,但不能链接:undefined referrence to '__device_dts_ord_N'

            • 可能是 Kconfig 配置问题导致了引用不存在,停止了驱动的构建
        • 因为它是在编译期创建的设备对象,没有运行时成本,但调用时不能确定一定初始化完成了,所以要再判断一下
        • DEVICE_DT_GET(device)的变体
      • 通过 device_get_binding()接口在运行时获得设备引用

        • 有时在构建时不能知道是什么设备,比如通过 shell 用户输入设备名,这时可以用 device_get_binding()
          const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
        
        • DT_LABEL(node_id) 得到的是node_id的属性label的值,相当于 DT_PROP(node_id, label)
        • 取到了devcie后,就可以和 UART API(如 uart_config())一起用

找到设备树对应的绑定

  • 只有知道了yaml绑定文件,才知道怎么用设备树节点。所以我们要知道怎么找到一个设备树节点node的绑定
  • 我们首先拿到代码,然后生成头文件
  • 设备树的绑定在构建系统最后生成的 zephyr.dts 文件中(https://docs.zephyrproject.org/latest/build/dts/howtos.html#get-devicetree-outputs)
    • 这个文件是所有的设备树节点,记下一个感兴趣的标识符,比如memory@20000000
  • 然后在devicetree_unfixed.h文件中搜索它,就可以得到下面的内容

      /*
      * Devicetree node: /soc/memory@20000000
      *
      * Node identifier: DT_N_S_soc_S_memory_20000000
      *
      * Binding (compatible = mmio-sram):
      *   $ZEPHYR_BASE/dts/bindings/sram/mmio-sram.yaml
      *
      * (Descriptions have moved to the Devicetree Bindings Index
      * in the documentation.)
      */
    

    由此可以看到它的绑定文件

设置设备树覆盖层

  • CMake的 DTC_OVERLAY_FILE 指明要使用的覆盖层文件,c预处理器层依次包含它们
  • 设备树overlays文件会覆盖节点属性
    • <board>.dts文件

        /{
            soc {
                serial0: serial@40002000 {status = "okay"; current-speed=<115200>;};
            };
        };
      
    • 通过设备树overlays文件改写属性值

        &serial0 { current-speed = <9600>;};
      
    • 增加一些节点

        /{
            alias { my-serial = &serial0; };
            chosen { zephyr,console = &serial0; };
        };
      
    • 删除一些属性

        &serial0 {
            /delete-property/ 要删除的属性名;
        };
      
  • 上面是向设备树增加节点和属性的方法
    • 然后可以通过 Kconfig 启用驱动程序
    • 通过驱动程序以获得新增加的设备device结构
    • 最后通过 API 来操作设备

编写驱动程序

  • 驱动是为每个status = okay的设备树节点创建struct device对象
    • 这个设备树节点具有驱动想支持的compatible属性
  • 首先为驱动想支持的设备定义一个设备树绑定,以便构建系统创建出struct devcie

      description: 对设备的说明
      compatible: "xxx-company,yyy-device"
      include: base.yaml
    

    我们可以通过 devicetree_unfixed.h 来找想要的设备节点绑定的样本(find a devicetree binding)

  • 再写驱动程序
    • c代码可以用devicetree.h中的API查找所有需要兼容的 status = "okey" 的节点
    • 为每个节点实例化一个 struct device(用实例编号或节点标签)
    • 惯例
      • 每个设备的初始配置都应该是来自设备树属性的值,这样我们之后可以用devicetree overlay来改写
  • 具体的驱动模板

    • 准备驱动所需的数据

        #include <zephyr/drivers/some_api.h>
      
        // 定义对象的数据(RAM)
        struct my_dev_data { ... };
        // 定义对象的配置(ROM)
        struct my_dev_cfg { uint32_t freq; ... };
        // 定义对象的api
        static int my_drv_api_f1(const struct device *dev, uint32_t *xxx) { ... }
        static int my_drv_api_f2(const struct device *dev, uint64_t *yyy) { ... }
        static struct some_api my_api_funs = { 
            .func1 = my_drv_api_f1,
            .func2 = my_drv_api_f2,
        };
      
    • 创建设备struct device结构体

      • 用实例号创建设备,假设驱动支持的兼容compatible = “vnd,my-devcie”
        • 首先,定义 DT_DRV_COMPT

            #define DT_DRV_COMPT vnd_my_device
          
        • 然后,定义一个实例化宏,它用实例编号来创建设备结构体

            #define CREATE_MY_DEVICE(inst)	\
                static struct my_dev_data my_data_##inst = {  	\
                    .freq = DT_INST_PROP(inst, clock_frequency),\
                };	\
                static const struct my_dev_cfg my_cfg_##inst = { 	\
                    ...		\
                };		\
                DEVICE_DT_INST_DEFINE(inst, my_dev_init_function, 	\
                                        NULL, &my_data_##inst,		\
                                        &my_cfg_##inst,		\
                                        MY_DEV_INIT_LEVEL, 	\
                                        MYDEV_INIT_PRIORITY, \
                                        &my_api_funcs);
          
          • DT_INST_PROP()访问DT_DRV_COMPT定义的设备节点的属性值
        • 最后,把实例化宏给DT_INST_FOREACH_STATUS_OKAY(),让它去自动实例化多个对象

            DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE)
          
      • 用节点标签创建设备
        • 有些设备不能用实例号(SoC外围驱动器,它的初始化由供应商的HAL API完成)
        • 可以用DT_NODELABEL()引用设备树中SoC支持的外设的单个节点
        • 再用devicetree.h中的API来访问节点数据
        • 要求SoC的dsti文件为节点定义好了标签

            / {
                soc {
                    mydevice0: dev@0 { compatible = "vnd,my-device";};
                    mydevice1: dev@1 { compatible = "vnd,my-device";};
                };
            };
          
        • 驱动用节点label来访问

            #define MY_DEV(idx) DT_NODELABEL(mydevice##idx)
          
            #define CREATE_MY_DEVICE(idx) 	\
                static struct my_dev_data my_data_##idx = {	\
                    .freq = DT_PROP(MY_DEV(idx), clock_frequency),	\
                };		\
                static const struct my_dev_cfg my_cfg_##idx = {...};\
                DEVICE_DT_DEFINE(MY_DEV(idx), 	\
                                 my_dev_init_function, 	\
                                        NULL, &my_data_##inst,	\
                                        &my_cfg_##inst,	\
                                        MY_DEV_INIT_LEVEL, 	\
                                        MYDEV_INIT_PRIORITY, \
                                        &my_api_funcs)
          
        • 最后,手动检测每个设备树节点是否启用,再手动创建设备实例对象

            #if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice0), okay)
            CREATE_MY_DEVICE(0)
            #endif
          
            #if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice1), okay)
            CREATE_MY_DEVICE(1)
            #endif
          

依赖于其他设备的设备驱动

  • 一个设备依赖另一个设备,并需要一个指向它的指针(传感器需要指向SPI总线控制器设备的指针)

依赖主板上特定设备的应用程序

  • 让应用程序可以不修改地运行的一种方法是通过设备树别名来代替主板上的特定部分

设备树使用时问题的解决

  • 用pristine构建
  • 确保包含了devicetree.h
  • 确保在c代码中把compatile的名称改成小写和_
  • 检查预处理器的输出

      west build -b BOARD samples/hello_world -- -DEXTRA_CFLAGS=-save-temps=obj
    
  • 验证属性
    • 当读取某个节点出现编译错误时,我们要检查node_id和属性

        #if !DT_NODE_EXISTS(DT_NODELABEL(my_serial))
        #error "whoops"
        #endif
      
    • 看看这个属性是不是存在

        DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), clock_frequency)
      
    • 看看节点是不是有匹配的绑定

      • zephyr.dts -> devicetree_unfixed.h
      • 属性是不是定义了这个属性
  • 节点缺少绑定
    • 节点的compatible没有定义
    • 节点的compatible没有和绑定匹配上,在设备树dts文件中,compatible = "vnd,some-device"
    • 自定义的绑定文件所在目录可以通过DTS_ROOT设定

设备树和Kconfig的区别

  • 设备树是用来描述硬件,以及zephyr启动时的初始值,比如外设、启动时钟频率
  • Kconfig用来配置一些软件模块的支持,比如网络支持
  • 主板有2个UART设备
    • 设备树
      • 用2个UART节点描述主板上有UART硬件的事实
      • 设备树节点描述了UART的设置,比如寄存器地址
      • 系统在boot阶段会使用设备树的配置,比如波特率
    • Kconfig
      • 是否在构建版本中包含对UART的软件支持,是通过Kconfig控制的
      • 不用UART的应用程序,可以用Kconfig从构建中删除驱动程序源码
  • 设备树主要针对硬件,Kconfig主要裁剪软件(Kconfig -> #define)
    • 但Kconfig不能灵活控制驱动程序的配置参数,有时也可以在设备树中定义(比如缓冲区大小)
    • 主表明这些属性是针对Zephyr驱动程序的,而不是硬件配置,这些属性应该以zephyr开头,比如zephyr,random-mac-address
    • 设备对的/chosen节点,可以让用户选择硬件设备的特定实例,比如选择一个特定的UART作系统控制台

Pin Control

  • Pin Control API
  • Pin Controller:引脚控制器,控制引脚复用和引脚配置参数(如引脚方向、上拉/下拉电阻等)的硬件块
  • 引脚控制器的用户是SoC的外设控制器,因为它们可以外发信号(I2C0控制器的SDA信号映射到引脚Px0,设置这个引脚的频率甚至一些防抖/低功耗等高级功能)
  • 在这么多soc内部都包含有pin控制顺路,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性
    • 内核引入pinctrl系统,目的是为了统一各SOC厂商的pin脚管理
      • 解决各种芯片配置GPIO有自己特定配置方法的问题
      • 配置IO、操作IO不需要息配置GPIO控制器
      • 结合电源管理机制,可以实现不同状态下,IO功能的自动切换
        • 正常工作状态 :UART功能
        • 休眠状态(sleep1):普通输入,上位电阻
        • 休眠状态(sleep2):普通输入,下位电阻
    • 通过pinctrl系统管理所有SOC的管脚
    • 并对外设管脚如串口、I2C、SPI、LCD都有相应的配置模式
  • pinctrl专业术语
    • pin function:引脚功能,如GPIO/spi0/i2c0
    • pin group: 由多个引脚组合成的一组引脚集合
      • SPI0接口,由4个IO引脚:MISO、MOSI、SCK、CS
    • pin control state
      • 如果设备有电源管理功能(idel/sleep状态),我们希望不同状态下IO引脚功能配置不一样,省电
      • 需要给设备用到的IO引脚提供两种功能配置
      • 一个pin control state对应pin脚的一种配置
      • 一组pin脚可以配置多个状态
    • pin control state holder
      • 为了管理设备的pin control state,内核使用了pin control state holder来管理一个设备的所有pin control state
      • 通过holder可以得到设备用到的IO组引脚编号、功能配置等信息
    • 驱动在应用pin control子系统接口只有三个步骤
      • (找管理员)驱动加载或运行时,获取pin control state holder的句柄
      • (从管理员那里找到状态控制)设定pin control的状态
      • (把找到状态设置到硬件中)驱动退出时,释放pin control state holder的句柄
  • pin control state与设备树的关系
  • 这种引脚复用的功能,有集中式和分布式两种实现电路
    • 集中式是把所有控制器的引脚和Px0管脚连接到一个“矩阵开关”,由它来切换通路
    • 分布式是外设控制器的每个引脚都有和Px0管脚的单独通路,通路上有一个开关控制通断
  • 设置 CONFIG_PINCTRL_DYNAMIC 可以启用改变引脚配置的功能
    • 动态引脚控制应该只用于尚未初始化的设备
    • 由于 Zephyr 还不支持设备反初始化
    • 在设备运行时更改引脚配置可能导致意外行为
    • 原理
      • pinctrl_dev_config 将存储在 RAM 中而非ROM
      • 用户可以使用 pinctrl_update_States() 用一个新的集合更新存储在 pinctrl_dev_config 中的状态

GPIO和Pin Controller的区别

  • GPIO对PIN的通用控制、开关、读取逻辑电平
  • Pin Control实现管脚功能复用、参数配置、高级功能选择

Pin Contrl 总结

  • pinctrl是为了用统一的方法对不同的SoC配置GPIO的辅助功能AF
  • pinctrl用配置状态来表示多个GPIO的引脚配置集

      test_device0: test_device@0 {
          compatible = "vnd,pinctrl-device";
          reg = <0x0 0x1>;
          pinctrl-0 = <&test_device0_default>;
          pinctrl-1 = <&test_device0_sleep>;
          pinctrl-names = "default", "sleep";
      };
    
    • 这里就有2个状态default和sleep,对应着引脚配置集pinctrl-0和pinctrl-1
  • pin control驱动要实现函数pinctrl_configure_pins()

      int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintptr_t reg)
    
    • pins:要配置的引脚数组
    • pin_cnt: 引脚数量
    • pinctrl_soc_pin_t是由厂商定义的结构,厂商也会提供解析这个类型的api
    • 厂商还要定义Z_PINCTRL_STATE_PINS_INIT(node_id, prop)宏,

        #define Z_PINCTRL_STATE_PIN_INIT(node_id, prop, idx)			       \
            (DT_PROP_BY_IDX(node_id, prop, idx) |				       \
            ((GD32_PUPD_PULLUP * DT_PROP(node_id, bias_pull_up))		       \
            << GD32_PUPD_POS) |						       \
            ((GD32_PUPD_PULLDOWN * DT_PROP(node_id, bias_pull_down))	       \
            << GD32_PUPD_POS) |						       \
            ((GD32_OTYPE_OD * DT_PROP(node_id, drive_open_drain))		       \
            << GD32_OTYPE_POS) |						       \
            (DT_ENUM_IDX(node_id, slew_rate) << GD32_OSPEED_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_CHILD_VARGS(DT_PHANDLE(node_id, prop), \
                        DT_FOREACH_PROP_ELEM, pinmux,   \
                        Z_PINCTRL_STATE_PIN_INIT)}
      
      • 比如gd32的宏就是为每个group中的pinmux计算出pinctrl_soc_pin_t类型的值

          group1 {
              pinmux = <GD32_PINMUX_AF('A', 0, AF0)>,
                  <GD32_PINMUX_AF('B', 1, AF1)>;
          };
          group2 {
              pinmux = <GD32_PINMUX_AF('C', 2, AF2)>;
              drive-push-pull;
          };
        
  • 写pinctrl的驱动
    • 使用PINCTRL_DT_DEFINE(node_id)PINCTRL_DT_INST_DEFINE(inst)定义pinctrl配置:状态和引脚

对 GD32 usart 的分析

usart 绑定

  • zephyr/dts/bindings/serial/gd,gd32-usart.yaml

      description: GigaDevice USART
    
      compatible: "gd,gd32-usart"
    
      include: [uart-controller.yaml, pinctrl-device.yaml]
    
      properties:
          reg:
          required: true
    
          interrupts:
          required: true
    
          rcu-periph-clock:
          type: int
          description: Reset Control Unit Peripheral Clock ID
          required: true
    

设备树

  • gd32f4xx.dtsi

      usart0: usart@40011000 {
          compatible = "gd,gd32-usart";
          reg = <0x40011000 0x400>;
          interrupts = <37 0>;
          rcu-periph-clock = <0x1104>;
          status = "disabled";
          label = "USART_0";
      };
    
  • zephyr/boards/arm/gd32f450i_eval/gd32f450i_eval.dts

      &usart0 {
          status = "okay";
          current-speed = <115200>;
          pinctrl-0 = <&usart0_default>;
          pinctrl-names = "default";
      };
    
    • zephyr/boards/arm/gd32f450i_eval/gd32f450i_eval-pinctrl.dtsi

        &pinctrl {
            usart0_default: usart0_default {
                group1 {
                    pinmux = <USART0_TX_PA9>, <USART0_RX_PA10>;
                };
            };
      
        }
      
    • modules/hal/gigadevice/include/dt-bindings/pinctrl/gd32f450i(g-i-k)xx-pinctrl.h

        #define USART0_TX_PA9 GD32_PINMUX_AF('A', 9, AF7)
        #define USART0_RX_PA10 GD32_PINMUX_AF('A', 10, AF7)
      
        #define GD32_PINMUX_AF(port, pin, af)	\
            (((((port) - 'A') & GD32_PORT_MSK) << GD32_PORT_POS) |	\
            (((pin) & GD32_PIN_MSK) << GD32_PIN_POS) |	\
            (((GD32_ ## af) & GD32_AF_MSK) << GD32_AF_POS))
      
    • 最后得到的结果build/zephyr/zephyr.dts

        usart0: usart@40011000 {
            compatible = "gd,gd32-usart";
            reg = < 0x40011000 0x400 >;
            interrupts = < 0x25 0x0 >;
            rcu-periph-clock = < 0x1104 >;
            status = "okay";
            label = "USART_0";
            current-speed = < 0x1c200 >;
            pinctrl-0 = < &usart0_default >;
            pinctrl-names = "default";
        };
      

驱动程序

下面是usart_gd32.c对usart实例化的部分,首先通过 PINCTRL_DT_INST_DEFINE(n)定义引脚控制配置(pin control configuration)

#define GD32_USART_INIT(n)							\
	PINCTRL_DT_INST_DEFINE(n);						\
	GD32_USART_IRQ_HANDLER(n)						\
	static struct gd32_usart_data usart_gd32_data_##n = {			\
		.baud_rate = DT_INST_PROP(n, current_speed),			\
	};									\
	static const struct gd32_usart_config usart_gd32_config_##n = {		\
		.reg = DT_INST_REG_ADDR(n),					\
		.rcu_periph_clock = DT_INST_PROP(n, rcu_periph_clock),		\
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\
		.parity = DT_INST_ENUM_IDX_OR(n, parity, UART_CFG_PARITY_NONE),	\
		 GD32_USART_IRQ_HANDLER_FUNC_INIT(n)				\
	};									\
	DEVICE_DT_INST_DEFINE(n, &usart_gd32_init,				\
			      NULL,						\
			      &usart_gd32_data_##n,				\
			      &usart_gd32_config_##n, PRE_KERNEL_1,		\
			      CONFIG_SERIAL_INIT_PRIORITY,			\
			      &usart_gd32_driver_api);

DT_INST_FOREACH_STATUS_OKAY(GD32_USART_INIT)
  • PINCTRL_DT_INST_DEFINE(n)展开的结果

      // 引脚配置集,每一个引脚配置由一个pinctrl_soc_pin_t代表
      static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_53[] = {
          (1936 | ((1U * 0) << 29U) | ((2U * 0) << 29U) | ((1U * 0) << 28U) |
          (0 << 26U)),
          (1952 | ((1U * 0) << 29U) | ((2U * 0) << 29U) | ((1U * 0) << 28U) |
          (0 << 26U)),
      };
      // 引脚控制设备具有的多个状态,每个状态代表一组引脚配置集
      static const struct pinctrl_state __pinctrl_states__device_dts_ord_53[] = {
          {.id = 0U,
          .pins = __pinctrl_state_pins_0__device_dts_ord_53,
          .pin_cnt = 1U,
          }
      };
      // 引脚控制器设备配置信息:状态(配置集)数组,数组元素个数
      static const struct pinctrl_dev_config __pinctrl_dev_config__device_dts_ord_53 = {
          .states = __pinctrl_states__device_dts_ord_53,
          .state_cnt = 1U,
      };
    
  • 第2段

      static struct gd32_usart_data usart_gd32_data_##n = {	\
          .baud_rate = DT_INST_PROP(n, current_speed),    \
      };									\
    
      static struct gd32_usart_data usart_gd32_data_0 = {
          .baud_rate = 115200,
      };
    
  • 第3段

      static const struct gd32_usart_config usart_gd32_config_##n = {	\
          .reg = DT_INST_REG_ADDR(n),	\
          .rcu_periph_clock = DT_INST_PROP(n, rcu_periph_clock),	\
          .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),  \
          .parity = DT_INST_ENUM_IDX_OR(n, parity, UART_CFG_PARITY_NONE),	\
              GD32_USART_IRQ_HANDLER_FUNC_INIT(n)    \
      };
    
      static const struct gd32_usart_config usart_gd32_config_0 = {
          .reg = 1073811456,
          .rcu_periph_clock = 4356,
          .pcfg = &__pinctrl_dev_config__device_dts_ord_53,
          .parity = UART_CFG_PARITY_NONE,
      };
    
  • 第4段

      DEVICE_DT_INST_DEFINE(n, &usart_gd32_init,				\
                    NULL,						\
                    &usart_gd32_data_##n,				\
                    &usart_gd32_config_##n, PRE_KERNEL_1,		\
                    CONFIG_SERIAL_INIT_PRIORITY,			\
                    &usart_gd32_driver_api);
      DT_INST_FOREACH_STATUS_OKAY(GD32_USART_INIT)
        
      static struct device_state __devstate_dts_ord_53 __attribute__((__section__(".z_devstate")));
    
      extern const device_handle_t __devicehdl_DT_N_S_soc_S_usart_40011000[];
    
      const device_handle_t __attribute__((__weak__, __section__(".__device_handles_pass1")))
          __devicehdl_DT_N_S_soc_S_usart_40011000[] = { 
              53, 7, 22, 52,
              (-0x7fff - 1) ,
              (-0x7fff - 1) , };
    
      const __attribute__((__aligned__(__alignof( struct device)))) struct device __device_dts_ord_53
          __attribute__((__section__(".z_device_" "PRE_KERNEL_1" "55" "_"))) = {
              .name = "USART_0",
              .config = (&usart_gd32_config_0),
              .api = (&usart_gd32_driver_api),
              .state = (&__devstate_dts_ord_53),
              .data = (&usart_gd32_data_0),
              .handles = __devicehdl_DT_N_S_soc_S_usart_40011000,
      };
    
      static const __attribute__((__aligned__(__alignof( struct init_entry))))
          struct init_entry __init___device_dts_ord_53
          __attribute__((__used__))
          __attribute__((__section__(".z_init_" "PRE_KERNEL_1" "55" "_"))) = {
              .init = (&usart_gd32_init),
              .dev = ((&__device_dts_ord_53)),
      };
    
  • usart_gd32_init()

      static int usart_gd32_init(const struct device *dev) {
    
          const struct gd32_usart_config *const cfg = dev->config;
          struct gd32_usart_data *const data = dev->data;
          uint32_t word_length;
          uint32_t parity;
          int ret;
    
          ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
          if (ret < 0) {
              return ret;
          }
    
          /**
          * In order to keep the transfer data size to 8 bits(1 byte),
          * append word length to 9BIT if parity bit enabled.
          */
          switch (cfg->parity) {
          case UART_CFG_PARITY_NONE:
              parity = USART_PM_NONE;
              word_length = USART_WL_8BIT;
              break;
          case UART_CFG_PARITY_ODD:
              parity = USART_PM_ODD;
              word_length = USART_WL_9BIT;
              break;
          case UART_CFG_PARITY_EVEN:
              parity = USART_PM_EVEN;
              word_length = USART_WL_9BIT;
              break;
          default:
              return -ENOTSUP;
          }
    
          rcu_periph_clock_enable(cfg->rcu_periph_clock);
          usart_deinit(cfg->reg);
          usart_baudrate_set(cfg->reg, data->baud_rate);
          usart_parity_config(cfg->reg, parity);
          usart_word_length_set(cfg->reg, word_length);
          /* Default to 1 stop bit */
          usart_stop_bit_set(cfg->reg, USART_STB_1BIT);
          usart_receive_config(cfg->reg, USART_RECEIVE_ENABLE);
          usart_transmit_config(cfg->reg, USART_TRANSMIT_ENABLE);
          usart_enable(cfg->reg);
    
          return 0;
      }
    
    • 其中pinctrl_apply_state(status_id)让pin脚控制器应用status_id状态
      • pinctrl有多个状态,每个状态是一个引脚配置集,它带有一个引脚配置数组
      • 通过id来找到要应用的状态
      • 通过pinctrl_apply_state_direct()应用这个找到的状态
      • 代码如下

          static inline int pinctrl_apply_state(const struct pinctrl_dev_config *config, uint8_t id)
          {
              int ret;
              const struct pinctrl_state *state;
        
              // 找到id对应的pinctrl中的状态
              ret = pinctrl_lookup_state(config, id, &state);
              if (ret < 0) {
                  return ret;
              }
        
              // 通过pinctrl给状态集中所有的pin脚做配置
              return pinctrl_apply_state_direct(config, state);
          }
        
      • pinctrl_apply_state_direct实际是回调了gd32的pinctrl_configure_pins接口
    • pinctrl_gd32_af.c

        int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintptr_t reg)
        {
            ARG_UNUSED(reg);
      
            for (uint8_t i = 0U; i < pin_cnt; i++) {
                pinctrl_configure_pin(pins[i]);
            }
      
            return 0;
        }
      
        static void pinctrl_configure_pin(pinctrl_soc_pin_t pin)
        {
            uint8_t port_idx;
            uint32_t rcu, port, pin_num, af, mode;
      
            port_idx = GD32_PORT_GET(pin);
            __ASSERT_NO_MSG(port_idx < ARRAY_SIZE(gd32_port_addrs));
      
            rcu = gd32_port_rcus[port_idx];
            port = gd32_port_addrs[port_idx];
            pin_num = BIT(GD32_PIN_GET(pin));
            af = GD32_AF_GET(pin);
      
            rcu_periph_clock_enable(rcu);
      
            if (af != GD32_ANALOG) {
                mode = GPIO_MODE_AF;
                gpio_af_set(port, af, pin_num);
            } else {
                mode = GPIO_MODE_ANALOG;
            }
      
            gpio_mode_set(port, mode, GD32_PUPD_GET(pin), pin_num);
            gpio_output_options_set(port, GD32_OTYPE_GET(pin), GD32_OSPEED_GET(pin), pin_num);
        }
      
    • 整个调用过程如下

      • API 调用 pinctrl

      • 数据图

        pinctrl1

设备树表示

引脚控制器的状态表示

  • 在设备树节点中,pinctrl-n表示第n个管脚的状态,pinctrl-name表示第n个管脚状态的名字

      periph0: periph@0 {
          // n个管脚的状态,默认为default
          pinctrl-0 = <...>;
          // ... 
          pinctrl-n = <...>;
    
          // n个状态的名字
          pinctrl-names = "default", ..., "mystate";
      };
    

引脚控制器的配置表示

  • 引脚的配置,就是复用功能和参数
    • UART_Rx 映射到 Px0 并配上拉电阻
  • 配置表示的方法,由供应商提供,所以引脚控制器配置去找设备树的绑定文件查找
  • 常用的配置表示

    • <board>.dts 设备树

        #include "board-pinctrl.dtsi"
      
        &periph0 {
            pinctrl-0 = <&periph0_default>;
            pinctrl-name = "default";
        };
      
    • board-pinctrl.dtsi 文件

        #include <vnd-soc-pkgxx.h>
      
        &pinctrl {
            periph0_default: periph0_default {
                group1 {
                    // 把periph0_siga映射为px0, periph0_sigc映射为pz1
                    pinmux = <PERIPH0_SIGA_PX0>, <PERIPH0_SIGC_PZ1>;
                    // px0, pz1都上拉
                    bias-pull-up;
                };
                ...
                groupN { pinmux = <PERIPH0_SIGB_PY7>; };
            };
        };
      
    • vnd-soc-pkgxx.h文件

        #define PERIPH0_SIGA_PX0 VNDSOC_PIN(X, 0, MUX0)
        #define PERIPH0_SIGB_PY7 VNDSOC_PIN(Y, 7, MUX4)
        #define PERIPH0_SIGB_PZ1 VNDSOC_PIN(Z, 1, MUX2)
      
    • 因为所有文件被解析成C头文件,保持它的小体积很重要
      • 在预定义的节点前加/omit-if-no-ref/可以在不使用时删除节点

          &pinctrl {
              /omit-if-no-ref/ periph0_default: periph0_default {
                  ...
              };
          };
        
    • 不要在预定义的节点中加默认值,它应该由主板来确定

驱动实现

  • 需要实现一个函数 pinctrl_configure_pins()
  • 引脚的配置保存在结构体 pinctrl_soc_pin_t 中,在 pinctrl_soc.h 中定义
  • pinctrl_soc.h中还有一个宏:Z_PINCTRL_STATE_PINS_INIT,它给节点中所有pinctrl-x引脚配置一个初始化器
  • 修改驱动文件对应的绑定文件,在绑定中修改设备兼容性,以便包含 pinctrl-device.yaml:

      include: [base.yaml, pinctrl-device.yaml]
    
    • 这个绑定文件需要添加 pinctrl-npinctrl-name
  • 对于驱动程序,要使用pinctrl的API,有两个步骤
    • 定义pinctrl配置:PIN_DT_DEFINEPIN_DT_INST_DEFINE
      • 所有引脚
      • 所有配置
    • 保存设备实例pinctrl_dev_config的引用
      • PINCTRL_DT_DEV_CONFIG_GET
      • PINCTRL_DT_INST_DEV_CONFIG_GET
    • 一个设备和它相关的引脚控制配置之间的唯一关系是基于变量的命名惯例
  • 驱动代码

      // 一个兼容 "mydev" 设备的驱动
      #define DT_DRV_COMPT mydev
    
      #include <zephyr/drivers/pinctrl.h>
    
      struct mydev_config {
          ... 
          // mydev pinctrl config
          const struct pinctrl_dev_config *pcfg;
      };
    
      static int mydev_init(const struct device *dev) {
          const struct mydev_config *config = dev->config;
          int ret;
          ...
          // 初始化阶段设定一个默认状态
          ret = pinctrl_app_state(config->pcfg, PINCTRL_STATE_DEFAULT);
          if (ret < 0) {return ret;}
          ...
      }
    
      #define MYDEV_DEFINE(i)                                     \
          // Define all pinctrl configuration for instance "i"    \
          PINCTRL_DT_INST_DEFINE(i);                              \
          ...                                                     \
          static const struct mydev_config mydev_config_##i = {   \
              ...                                                 \
              .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i),          \ 
          };                                                      \
          ...                                                     \
          DEVICE_DT_INST_DEFINE(i, mydev_init, NULL, 
                                  &mydev_data##i,                 \
                                  &mydev_config##i,               \
                                  ...);                           \
      DT_INST_FOREACH_STATUS_OKAY(MYDEV_DEFINE)
    

移植的步骤

创建主板目录

  • 拷贝已有目录

      cp -r zephyr/boards/arm/gd32f450i_eval zephyr/boards/arm/mepm_tcb16
    
  • 修改内容

    • 把各个文件名中的gd32f450i_eval修改成mepm_tcb16
    • Kconfig.board

        config BOARD_MEPM_TCB16
            bool "GigaDevice MEPM-TCB16"
            depends on SOC_GD32F450
      
      • 创建一个BOARD_MEPM_TCB16的bool选项
    • Kconfig.deconfig

        if BOARD_MEPM_TCB16
      
        config BOARD
            default "mepm_tcb16"
      
        endif # BOARD_MEPM_TCB16
      
      • 如果定义了BOARD_MEPM_TCB16,那么 #define BOARD "mepm_tcb16"
    • mepm_tcb16_defconfig

        CONFIG_BOARD_MEPM_TCB16=y
      

      #define BOARD_MEMP_TCB16

移植各个例程

  • 注意各个例程中专属CMakeLists.txtprj.conf
  • 当构建中出现undefined reference to __device_dts_ord_N’的错误时,可能是prj.conf`没有移植的原因

GD32F450z 开发板的分析

设备树

gd32f450z_eval.dts

首先分析 zephyr/boards/arm/gd32f450z_eval/gd32f450z_eval.dts 设备树,它引用了 gigadevice/gd32f4xx/gd32f450ik.dtsi内容如下 :

#include <mem.h>
#include <gigadevice/gd32f4xx/gd32f450.dtsi>

/ {
	soc {
		flash-controller@40023c00 {
			flash0: flash@8000000 {
				reg = <0x08000000 DT_SIZE_K(3072)>;
			};
		};
	};
};

其中mem.h如下

#ifndef __DT_MEM_H
#define __DT_MEM_H

#define DT_SIZE_K(x) ((x) * 1024)
#define DT_SIZE_M(x) ((x) * 1024 * 1024)

/* concatenate the values of the arguments into one */
#define _DT_DO_CONCAT(x, y) x ## y

#define DT_ADDR(a) _DT_DO_CONCAT(0x, a)

#endif /* __DT_MEM_H */

flash-controller@40023c00 {
    flash0: flash@8000000 {
        reg = <0x08000000 DT_SIZE_K(3072)>;
    };
};

定义了一个节点/soc/flash-controller@40023c00,这个40023c00就是FMC地址

gd32f450.dtsi

#include <gigadevice/gd32f4xx/gd32f4xx.dtsi>

/ {
	soc {
		spi3: spi@40013400 {
			compatible = "gd,gd32-spi";
			reg = <0x40013400 0x400>;
			interrupts = <84 0>;
			rcu-periph-clock = <0x110d>;
			status = "disabled";
			label = "SPI_3";
			#address-cells = <1>;
			#size-cells = <0>;
		};

		spi4: spi@40015000 {
			compatible = "gd,gd32-spi";
			reg = <0x40015000 0x400>;
			interrupts = <85 0>;
			rcu-periph-clock = <0x1114>;
			status = "disabled";
			label = "SPI_4";
			#address-cells = <1>;
			#size-cells = <0>;
		};

		spi5: spi@40015400 {
			compatible = "gd,gd32-spi";
			reg = <0x40015400 0x400>;
			interrupts = <86 0>;
			rcu-periph-clock = <0x1115>;
			status = "disabled";
			label = "SPI_5";
			#address-cells = <1>;
			#size-cells = <0>;
		};
	};
};
  • 每个中断生成设备包含一个 interrupts 中断属性,其值描述该设备的一个或多个中断源
  • 每个源都用称为interrupt specifier中断指定符的信息表示
  • 中断指定符的格式和意义是中断域特定的,也就是说,它依赖于中断域根节点上的属性
  • 中断域的根使用 #interrupt-cells 属性来定义编码中断指定符所需的 <u32> 值的数量

  • compatible = "gd,gd32-spi";还可以说明它的绑定文件是gd,gd32-spi.yaml
  • 分析 interrupts = <86 0>;
    • 查看build/zephyr/zephyr.dts

      soc {
          #address-cells = < 0x1 >;
          #size-cells = < 0x1 >;
          compatible = "simple-bus";
          interrupt-parent = < &nvic >;
          ranges;
          nvic: interrupt-controller@e000e100 {
              #address-cells = < 0x1 >;
              compatible = "arm,v7m-nvic";
              reg = < 0xe000e100 0xc00 >;
              interrupt-controller;
              #interrupt-cells = < 0x2 >;
              arm,num-irq-priority-bits = < 0x4 >;
              phandle = < 0x1 >;
          };
      
      • interrupt-parent=<&nvic>
        • 规定了本soc的中断父是nvic
      • nvic: interrupt-controller…
        • 这个结节的interrup-controller说明自己是中断控制器
        • #interrupt-cells = <0x2>说明中断说明符有两个单元
    • 所以interrupts = <86 0>就是nvic规定的

      • 86是指的IRQ86:IRQ86 102 SPI5 全局中断
      • 0 是中断优先级

驱动分析

pwm-led 驱动分析

  • 首先,搜索*pwm*.c文件,可以找到pwm_gd32.c,这就是pwm相关的驱动
  • 看文件最下面有一行代码,这就是注册pwm驱动的地方

      DT_INST_FOREACH_STATUS_OKAY(PWM_GD32_DEFINE)
    
  • DT_INST_FOREACH_STATUS_OKAY()
    • 在所有具有兼容DT_DRV_COMPAT和状态“OK”的节点上调用“fn”,相当于fn(inst)
    • 当设备树有多个compatible = “vnd,model`时,自动给每个status = “okay”的节点实例化

        a { compatible = "vnd,device"; status = "okay"; label = "DEV_A"; };
        b { compatible = "vnd,device"; status = "okay"; label = "DEV_B"; };
        c { compatible = "vnd,device"; status = "disabled"; label = "DEV_C"; };
      

      在驱动中就可以用:

        #define DT_DRV_COMPAT vnd_device
        #define MY_FN(inst) DT_INST_LABEL(inst),
      
        DT_INST_FOREACH_STATUS_OKAY(MY_FN)
      

      扩展成

        MY_FN(0) MY_FN(1)
      

PWM_GD32_DEFINE 的分析

#define PWM_GD32_DEFINE(i)						       \
	static struct pwm_gd32_data pwm_gd32_data_##i;			       \
	PINCTRL_DT_INST_DEFINE(i);					       \
									       \
	static const struct pwm_gd32_config pwm_gd32_config_##i = {	       \
		.reg = DT_REG_ADDR(DT_INST_PARENT(i)),			       \
		.rcu_periph_clock = DT_PROP(DT_INST_PARENT(i),		       \
					    rcu_periph_clock),		       \
		.rcu_periph_reset = DT_PROP(DT_INST_PARENT(i),		       \
					    rcu_periph_reset),		       \
		.prescaler = DT_PROP(DT_INST_PARENT(i), prescaler),	       \
		.channels = DT_PROP(DT_INST_PARENT(i), channels),	       \
		.is_32bit = DT_PROP(DT_INST_PARENT(i), is_32bit),	       \
		.is_advanced = DT_PROP(DT_INST_PARENT(i), is_advanced),	       \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i),		       \
	};								       \
									       \
	DEVICE_DT_INST_DEFINE(i, &pwm_gd32_init, NULL, &pwm_gd32_data_##i,     \
			      &pwm_gd32_config_##i, POST_KERNEL,	       \
			      CONFIG_KERNEL_INIT_PRIORITY_DEVICE,	       \
			      &pwm_gd32_driver_api);
  • 首先定义一个pwm_gd32_data的结构体
  • PINCTRL_DT_INST_DEFINE(i)为给定的兼容索引(compatible idx)定义所有引脚控制信息,展开成:

      static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_24[] = {
          (417 | ((1U * 0) << 29U) | ((2U * 0) << 29U) | ((1U * 0) << 28U) |
          (0 << 26U)),
      };
    

    这个pinctrl_soc_pin_t的内容如下:

      /** @brief Type for GD32 pin.
      *
      * Bits (AF model):
      * - 0-12: GD32_PINMUX_AF bit field.
      * - 13-25: Reserved.
      * - 26-31: Pin configuration bit field (@ref GD32_PINCFG).
      *
      * Bits (AFIO model):
      * - 0-19: GD32_PINMUX_AFIO bit field.
      * - 20-25: Reserved.
      * - 26-31: Pin configuration bit field (@ref GD32_PINCFG).
      */
      typedef uint32_t pinctrl_soc_pin_t;
    
  • 接下来定义一个pwm_gd32_config配置信息

      static const struct pwm_gd32_config pwm_gd32_config_##i = {	    \
          .reg = DT_REG_ADDR(DT_INST_PARENT(i)),			            \
          .rcu_periph_clock = DT_PROP(DT_INST_PARENT(i),		        \
                          rcu_periph_clock),		                    \
          .rcu_periph_reset = DT_PROP(DT_INST_PARENT(i),		        \
                          rcu_periph_reset),		                    \
          .prescaler = DT_PROP(DT_INST_PARENT(i), prescaler),	        \
          .channels = DT_PROP(DT_INST_PARENT(i), channels),	        \
          .is_32bit = DT_PROP(DT_INST_PARENT(i), is_32bit),	        \
          .is_advanced = DT_PROP(DT_INST_PARENT(i), is_advanced),	    \
          .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i),		            \
      };
    
    • 从这里可以看到,驱动是从设备树中获取所需的设备信息的,这里就是设备树与C代码的交接处
    • 这里DT_INST_PARENT(i)是获取DT_DRV_COMPAT 父节点的node_id. 展开如下 :
      static const struct pwm_gd32_config pwm_gd32_config_0 = {
          .reg = 1073741824, // 0x4000_0000: TIMER1 基地址:0x4000 0000
          .rcu_periph_clock = 4096,
          .rcu_periph_reset = 2048,
          .prescaler = 0,
          .channels = 4,
          .is_32bit = 1,
          .is_advanced = 0,
          .pcfg = &__pinctrl_dev_config__device_dts_ord_24,
      };
    
      static const struct pinctrl_dev_config __pinctrl_dev_config__device_dts_ord_24 = {
          .states = __pinctrl_states__device_dts_ord_24,
          .state_cnt = ...,
      };
    
      static const struct pinctrl_state __pinctrl_states__device_dts_ord_24[] = {
          {.id = 0U,
          .pins = __pinctrl_state_pins_0__device_dts_ord_24,
          .pin_cnt = ...}
      };
    
  • DEVICE_DT_INST_DEFINE

      DEVICE_DT_INST_DEFINE(i, &pwm_gd32_init, NULL, &pwm_gd32_data_##i,     \
                    &pwm_gd32_config_##i, POST_KERNEL,	       \
                    CONFIG_KERNEL_INIT_PRIORITY_DEVICE,	       \
                    &pwm_gd32_driver_api);
    
    • DEVICE_DT_INST_DEFINE -> Z_DEVICE_DEFINE ->
      • Z_DEVICE_DEFINE_PRE 定义出结构体 ` __device_dts_ord_24`并赋值

          Z_DEVICE_DEFINE_PRE(node_id, dev_name __VA_ARGS__)
          COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static))
          const Z_DECL_ALIGN(struct device) DEVICE_NAME_GET(dev_name) __used      __attribute__((__section__(".z_device_" #level STRINGIFY(prio) "_"))) = {
              .name = drv_name,
              .config = (cfg_ptr),
              .api = (api_ptr), 
              .state = (state_ptr),
              .data = (data_ptr),
              COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device, ), ())
              Z_DEVICE_DEFINE_INIT(node_id, dev_name) };
        
      • Z_INIT_ENTRY_DEFINE 把定义好的__device_dts_ord_24设备放到了init_entry段,这样在系统启动时就会初始化这个设备

pwm API 的分析

  • pwm_gd32_init

      static int pwm_gd32_init(const struct device *dev) {
          // 这里的config就是上面的 pwm_gd32_config_0
          const struct pwm_gd32_config *config = dev->config;
          // data = pwm_gd32_data_0
          struct pwm_gd32_data *data = dev->data;
          int ret;
    
          // 这些都是从设备树中获取的参数
          rcu_periph_clock_enable(config->rcu_periph_clock);
          rcu_periph_reset_enable(config->rcu_periph_reset);
          rcu_periph_reset_disable(config->rcu_periph_reset);
    
          /* apply pin configuration */
          ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
    
          /* cache timer clock value */
          data->tim_clk = pwm_gd32_get_tim_clk(dev);
    
          /* basic timer operation: edge aligned, up counting, shadowed CAR */
          TIMER_CTL0(config->reg) = TIMER_CKDIV_DIV1 | TIMER_COUNTER_EDGE |
                      TIMER_COUNTER_UP | TIMER_CTL0_ARSE;
          TIMER_PSC(config->reg) = config->prescaler;
    
          /* enable primary output for advanced timers */
          if (config->is_advanced) {
              TIMER_CCHP(config->reg) |= TIMER_CCHP_POEN;
          }
    
          /* enable timer counter */
          TIMER_CTL0(config->reg) |= TIMER_CTL0_CEN;
    
          return 0;
      }
    

led_pwm.c

  • 但是到这里,没有看到pwm和led有任何联系
  • 再找例程,zephyr/samples/basic/blinky_pwm/boards/esp32.conf,发现对于gd32的主板,可能还要打开一些配置开关。在application/tcb16/prj.conf中再加上配置还是没有呼吸灯

      CONFIG_LED=y
      CONFIG_LED_PWM=y
    
  • 通过搜索 LED_PWM 找到了zephyr/drivers/led/Kconfig.pwm这个文件,在这个目录中找到了驱动led_pwm.c。现在需要对它做一个分析

    • 文件开头有#define DT_DRV_COMPAT pwm_leds,说明它是对应compatible = "pwm_leds的驱动程序
  • 最后,在这里找到了原因。需要在硬件上把led连接到pwm的输出才会有呼吸灯
  • 之前编译后出现了段错误的原因在于,我用的是pwm_gd32.c的设备对象,而led_blink(dev)需要的是led_pwm.c的设置对象
    • pwm_gd32.c的对象是没有blink接口的,所以都会在运行时出现段错误
  • 那么,我如果想通过led驱动控制led应该怎么做呢?

led 驱动

  • 首先,在prj.conf中有一个配置项CONFIG_LED=y,从它就可以知道对应的驱动是*led*.c,一般是在drivers/led/这个目录中
  • 在目录中找到了led_gpio.c驱动代码,也看到与之对应的drivers/led/Kconfig.gpio配置文件,内容为:

      // 需要在prj.conf中设置CONFIG_LED_GPIO=y
      config LED_GPIO
          bool "GPIO LED driver"
          // 需要先有CONFIG_GPIO=y,并且设备树中有compatible="gpio-leds"的节点
          depends on GPIO && $(dt_compat_enabled,gpio-leds)
          help
              Enable driver for GPIO LEDs.
    
    • 现在我们就来配置一下我们的项目

        CONFIG_GPIO=y
        CONFIG_LED=y
        CONFIG_LED_GPIO=y
      
    • 这样就开启了led_gpio.c驱动功能

  • 在app中使用驱动

    • 从设备树获取 led 设备的名称

        #if DT_NODE_HAS_STATUS(DT_INST(0, gpio_leds), okay)
            #define LEDS_NODE_ID DT_INST(0, gpio_leds)
            #define LEDS_DEV_NAME DEVICE_DT_NAME(LEDS_NODE_ID)
        #else
            #error "No LED GPIO device found"
        #endif
      
      • 其中DT_INST(0, gpio_leds)是指设备树中compatible = "gpio-leds"的设备的第0个实例(inst = 0)的node_id
      • node_id相当于设备树节点的指针
    • 获取设备树节点的设备对象

        const struct device *leds = device_get_binding(LEDS_DEV_NAME);
        if (NULL == leds) {
            printk("没有找到LED_ALM设备\n");
            return;
        }
        if (!device_is_ready(leds)) {
            printk("告警灯未准备好\n");
            return;
        }
      
    • 从设备树中获取compatible实例的节点leds对象

        #define LEDS_NODE_ID DT_INST(0, gpio_leds)
        #define LEDS_DEV_NAME DEVICE_DT_NAME(LEDS_NODE_ID)
      
        const struct device *leds = device_get_binding(LEDS_DEV_NAME);
      
      • 这里leds是led设备集合对象,它的本质是

          static const struct gpio_dt_spec gpio_dt_spec_0[] = {
              { .port = (&__device_dts_ord_15), .pin = 13, .dt_flags = 0, },
              { .port = (&__device_dts_ord_15), .pin = 12, .dt_flags = 0, },
              { .port = (&__device_dts_ord_9), .pin = 14, .dt_flags = 0, },
              { .port = (&__device_dts_ord_9), .pin = 15, .dt_flags = 0, },
          };
                    
          static const struct led_gpio_config led_gpio_config_0 = {
              .num_leds = 4,
              .led = gpio_dt_spec_0,
          };
        
          // 这就是 leds 设备对象,它包含了4个led
          struct device __device_dts_ord_14= {
              .name = "leds",
              .config = (&led_gpio_config_0),
              .api = (&led_gpio_api),
              .state = (&__devstate_dts_ord_14),
              .data = ( ((void *)0)),
              .handles = __devicehdl_DT_N_S_leds,
          };
        
    • 使用 led_gpio.c 的驱动方法控制 led

        while (1) {
            led_on(leds, 1); // 这里1就是gpio_dt_spec_0[1]的led灯
            k_msleep(500);
            led_off(leds, 1);
            k_msleep(500);
        } 
      
      • 因为 led_gpio.cled_gpio_api 规定了这个led对象拥有的方法

          static const struct led_driver_api led_gpio_api = {
              .on = led_gpio_on,
              .off = led_gpio_off,
              .set_brightness = led_gpio_set_brightness,
          };
        
        • 虽然 led_driver_api 还有其他方法,但这个驱动中却没有实现,所以不能使用

            __subsystem struct led_driver_api {
                /* Mandatory callbacks. */
                led_api_on on;
                led_api_off off;
                /* Optional callbacks. 可选方法 */
                led_api_blink blink;
                led_api_get_info get_info;
                led_api_set_brightness set_brightness;
                led_api_set_color set_color;
                led_api_write_channels write_channels;
            };
          

GPIO 驱动

  • 控制led,还可以直接控制它的GPIO,控制过程如下
  • 我们直接使用特定硬件GPIO的驱动zephyr/drivers/gpio/gpio_gd32.c,上面使用的是 led 的驱动
  • 设备树内容

      leds {
          compatible = "gpio-leds";
    
          led_run: led1 { 
              gpios = <&gpioh 13 GPIO_ACTIVE_HIGH>; 
              label = "LED_RUN"; 
          };
          led_alm: led2 { 
              gpios = <&gpioh 12 GPIO_ACTIVE_HIGH>; 
              label = "LED_ALM"; 
          };
      };
    
  • 从设备树中获取设备对象

    • 从设备树中直接获取leds节点的子节点led_run的gpio对象

        #define LED_RUN_NODE DT_ALIAS(led_run)
      
        static const struct gpio_dt_spec led_run 
                    = GPIO_DT_SPEC_GET(LED_RUN_NODE, gpios);
      
      • 解析 GPIO_DT_SPEC_GET

          GPIO_DT_SPEC_GET = 
          #define GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx)			       \
          {								       \
              .port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)),\
              .pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx),		       \
              .dt_flags = DT_GPIO_FLAGS_BY_IDX(node_id, prop, idx),	       \
          }
        

        而其中 DT_GPIO_PIN_BY_IDX(node_id, prop, idx) 的作用是

        • Get a phandle-array specifier cell value at an index
        • 在索引处获取幻影数组说明符信元值
        • 对于设备树

            gpio0: gpio@... { #gpio-cells = <2>; };
          
            gpio1: gpio@... { #gpio-cells = <2>; };
          
            led: led_0 { gpios = <&gpio0 17 0x1>, <&gpio1 5 0x3>; };
          
        • 绑定文件

            gpio-cells:
                - pin
                - flags
          
        • C语言代码

            #define LED DT_NODELABEL(led)
          
            DT_PHA_BY_IDX(LED, gpios, 0, pin)   // 17
            DT_PHA_BY_IDX(LED, gpios, 1, flags) // 0x3
          
      • 最终结果

          static const struct gpio_dt_spec led_run = { 
              .port = (&__device_dts_ord_9), 
              .pin = 9, .dt_flags = 1, 
          };
        
      • 这里led_run就是一个gpio设备

    • 驱动gpio_gd32.c中最后一句实例化了所有gpio硬件

        #define DT_DRV_COMPAT gd_gd32_gpio
      
        DT_INST_FOREACH_STATUS_OKAY(GPIO_GD32_DEFINE)
      
      • 对于所有DT_DRV_COMPAT的设备,只要它的status=okay就实例化为设备对象struct devcie __device_dts_ord_X
        • 这里gd_gd32_gpio可以通过搜索 gd32-gpio 在设备树文件中找到对应的设备树节点
        • 比如在 gd32f4xx.dsti 中有

            gpioa: gpio@40020000 {
                compatible = "gd,gd32-gpio";
                gpio-controller;
                #gpio-cells = <2>;
                reg = <0x40020000 0x400>;
                rcu-periph-clock = <0xc00>;
                status = "disabled";
                label = "GPIOA";
            };
          
          • 那么只要在自己的配置中,把status="okay",就会把gpioa节点实例化为设备对象
          • 并且这个对象的名字__device_dts_ord_X.name = label = "GPIOA"
      • 实例化的全过程如下,在gpio_gd32.c.i文件中搜索 static const struct gpio_gd32_config可以找到:

          static const struct gpio_gd32_config gpio_gd32_config4 = {
              .common = ..., .reg = 1073878016, .rcu_periph_clock = 3077,
          };
          static struct gpio_gd32_data gpio_gd32_data4;
        
          static struct device_state __devstate_dts_ord_59
              __attribute__((__section__(".z_devstate")));
        
          extern const device_handle_t
              __devicehdl_DT_N_S_soc_S_pin_controller_40020000_S_gpio_40021400[];
        
          const device_handle_t 
          __devicehdl_DT_N_S_soc_S_pin_controller_40020000_S_gpio_40021400[] = {
              59, 8, (-0x7fff - 1) , (-0x7fff - 1) ,
          };
        
          // 这就是设备对象
          const struct device __device_dts_ord_59 = {
                  .name = "GPIOF",
                  .config = (&gpio_gd32_config4),
                  .api = (&gpio_gd32_api),
                  .state = (&__devstate_dts_ord_59),
                  .data = (&gpio_gd32_data4),
                  .handles =
                      __devicehdl_DT_N_S_soc_S_pin_controller_40020000_S_gpio_40021400,
          };
        
          static const struct init_entry __init___device_dts_ord_59= {
                  .init = (&gpio_gd32_init),
                  .dev = ((&__device_dts_ord_59)),
          };
        
    • 通过 gpio 设备对象来控制

        if (!device_is_ready(led_run.port)) { return; }
      
        int32_t ret = gpio_pin_configure_dt(&led_run, GPIO_OUTPUT_ACTIVE);
      
        while (1) {
            ret = gpio_pin_toggle_dt(&led_run);
            k_msleep(500);
        }
      
  • 要干的事
    • 先学习API手册

      可以在 gpio_dt_spec, GPIO_DT_SPEC_GET), gpio_pin_configure_dt() 找到相关的API,也就是我需要学习这些API来写驱动和应用程序

    • 了解所有SoC有哪些节点名(节点标签)

      • 节点的标签名可以在SoC.dtsi文件中找到,所以我们需要先看看用到的 SoC 设备树的文件内容

网口移植

  • zephyr的网络初始化是通过 net_if.c 中的 net_if_init() 函数进行的
  • 其中有一段代码反映了网络接口 iface 的初始化过程,而这个 iface 又是通过 _net_if_list_start 找到的;而它是通过 ETH_NET_DEVICE_DT_INST_DEFINE 创建的

      STRUCT_SECTION_FOREACH(net_if, iface) {
          init_iface(iface);
          if_count++;
      }
    
  • 最终我们通过下面的命令生成相关的工程,因为frdm_k64f有比较全的网口初始化过程

      west build -p -b frdm_k64f zephyr/samples/net/sockets/echo_server  -DEXTRA_CFLAGS=-save-temps=obj
    

网卡驱动的分析

设备树分析

  • 首先我们根据eth_mcux.c可以找到对应的驱动兼容模式 compatible = nxp,kinetis-ethernet
  • 然后在设备树文件nxp_k6x.dsti中可以找到网卡设备的节点

      enet: ethernet@400c0000 {
          compatible = "nxp,kinetis-ethernet";
          reg = <0x400c0000 0x620>;
          interrupts = <83 0>, <84 0>, <85 0>;
          interrupt-names = "TX", "RX", "ERR";
          status = "disabled";
          label = "ETH_0";
          phy-addr = <0>;
          clocks = <&sim KINETIS_SIM_CORESYS_CLK 0 0>;
          ptp: ptp {
              compatible = "nxp,kinetis-ptp";
              status = "disabled";
              interrupts = <82 0>;
              interrupt-names = "IEEE1588_TMR";
          };
      };
    
  • 再从frdm_k64f.dst设备树中找到网卡节点

      &enet {
          status = "okay";
          pinctrl-0 = <&enet_default>;
          pinctrl-names = "default";
          ptp {
              /* Be aware that PTC16 and PTC17 are also used for uart3 */
              status = "disabled";
              pinctrl-0 = <&ptp_default>;
              pinctrl-names = "default";
          };
      };
    
  • 再从frdm_k64f-pinctrl.dtsi得到pinctrl状态的具体定义

      enet_default: enet_default {
          group0 {
              pinmux = <RMII0_MDIO_PTB0>;
              drive-strength = "low";
              drive-open-drain;
              bias-pull-up;
              slew-rate = "fast";
          };
          group1 {
              pinmux = <RMII0_RXER_PTA5>,
                  <RMII0_RXD1_PTA12>,
                  <RMII0_RXD0_PTA13>,
                  <RMII0_CRS_DV_PTA14>,
                  <RMII0_TXEN_PTA15>,
                  <RMII0_TXD0_PTA16>,
                  <RMII0_TXD1_PTA17>,
                  <RMII0_MDC_PTB1>;
              drive-strength = "low";
              slew-rate = "fast";
          };
      };
    
  • 所以这个设备即使用了pinctrl也使用了interrupts

网络设备实例化分析

  • 网络设备实例通过DT_INST_FOREACH_STATUS_OKAY(ETH_MCUX_INIT)来定义
  • 其中ETH_MCUX_INIT是实例化网络设备的宏
    • ETH_MCUX_GEN_MAC(n)生成MAC地址

        static void generate_eth0_mac(uint8_t *mac_addr) {
            uint32_t id =
                (((SIM_Type *)(0x40047000u))->UIDH ^ ((SIM_Type *)(0x40047000u))->UIDMH ^
                ((SIM_Type *)(0x40047000u))->UIDML ^ ((SIM_Type *)(0x40047000u))->UIDL);
            mac_addr[0] = 0x00;
            mac_addr[0] |= 0x02;
            mac_addr[1] = 0x04;
            mac_addr[2] = 0x9f;
            mac_addr[3] = id >> 8;
            mac_addr[4] = id >> 16;
            mac_addr[5] = id >> 0;
            mac_addr[5] += 0;
        }
      
    • ETH_MCUX_PINCTRL_DEFINE(n) = PINCTRL_DT_INST_DEFINE(n),为给定的compatible idx定义所有引脚控制信息

        static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_87[] = {
            268436480 | (0 << (6U)) & (0x40U) | (1 << (0U)) & (0x1U)) | (1 << (1U)) & (0x2U)) |
                        ...,
            20972544 | ((0) << (6U))) & (0x40U)) |
                        ...,
        };
        static const struct pinctrl_state __pinctrl_states__device_dts_ord_87[] = {
            {.id = 0U,
            .pins = __pinctrl_state_pins_0__device_dts_ord_87,
            .pin_cnt = 8U,
            }
        };
        static const struct pinctrl_dev_config __pinctrl_dev_config__device_dts_ord_87 = {
            .states = __pinctrl_states__device_dts_ord_87,
            .state_cnt = 2,
        };
      
      • PINCTRL_DT_INST_DEFINE就是把设备树的pinctrl变成pnctrl_app_state()可以操作的参数
    • 声明网络所需的函数、缓冲区和资源

        static void eth##n##_config_func(void);
        static NOCACHE uint8_t tx_enet_frame_##n##_buf[NET_ETH_MAX_FRAME_SIZE];
        static NOCACHE uint8_t rx_enet_frame_##n##_buf[NET_ETH_MAX_FRAME_SIZE];
        static mdio_handle_t eth##n##_mdio_handle = {
            .resource.base = (ENET_Type *)DT_INST_REG_ADDR(n),
        };
        static phy_handle_t eth##n##_phy_handle = {.mdioHandle = &eth##n##_mdio_handle,};
      
      • 得到如下内容

          static void eth0_config_func(void);
          static uint8_t tx_enet_frame_0_buf[((1500 + sizeof(struct net_eth_hdr)))];
          static uint8_t rx_enet_frame_0_buf[((1500 + sizeof(struct net_eth_hdr)))];
          static mdio_handle_t eth0_mdio_handle = {
              .resource.base = (ENET_Type *)1074528256,
          };
          static phy_handle_t eth0_phy_handle = {
              .mdioHandle = &eth0_mdio_handle,
          };
        
    • 为网络设备准备数据和配置信息

        static struct eth_context eth##n##_context = {
            .base = (ENET_Type *)DT_INST_REG_ADDR(n),
            .config_func = eth##n##_config_func,
            .phy_addr = DT_INST_PROP(n, phy_addr),
            .phy_duplex = kPHY_FullDuplex,
            .phy_speed = kPHY_Speed100M,
            .phy_handle = &eth##n##_phy_handle,
            .tx_frame_buf = tx_enet_frame_##n##_buf,
            .rx_frame_buf = rx_enet_frame_##n##_buf,
            ETH_MCUX_PINCTRL_INIT(n)
            ETH_MCUX_PHY_GPIOS(n)
            ETH_MCUX_MAC_ADDR(n)
            ETH_MCUX_POWER(n)
        };
      
      • 内容如下

          static struct eth_context eth0_context = {
              .base = (ENET_Type *)1074528256,
              .config_func = eth0_config_func,
              .phy_addr = 0,
              .phy_duplex = kPHY_FullDuplex,
              .phy_speed = kPHY_Speed100M,
              .phy_handle = &eth0_phy_handle,
              .tx_frame_buf = tx_enet_frame_0_buf,
              .rx_frame_buf = rx_enet_frame_0_buf,
              .pincfg = &__pinctrl_dev_config__device_dts_ord_87,
              .mac_addr = {0},
              .generate_mac = generate_eth0_mac,
              .clock_dev = (&__device_dts_ord_10),
          };
        
      • 关系

        enet1

      • 准备缓冲区

          static NOCACHE __aligned(ENET_BUFF_ALIGNMENT) 
              enet_rx_bd_struct_t eth##n##_rx_buffer_desc[CONFIG_ETH_MCUX_RX_BUFFERS]; 
                                                     
          static NOCACHE __aligned(ENET_BUFF_ALIGNMENT) 
              enet_tx_bd_struct_t eth##n##_tx_buffer_desc[CONFIG_ETH_MCUX_TX_BUFFERS]; 
                                                     
          static uint8_t __aligned(ENET_BUFF_ALIGNMENT) 
              eth##n##_rx_buffer[CONFIG_ETH_MCUX_RX_BUFFERS][ETH_MCUX_BUFFER_SIZE]; 
                                                     
          static uint8_t __aligned(ENET_BUFF_ALIGNMENT) 
              eth##n##_tx_buffer[CONFIG_ETH_MCUX_TX_BUFFERS][ETH_MCUX_BUFFER_SIZE]; 
                                                     
          ETH_MCUX_PTP_FRAMEINFO_ARRAY(n) 
        
      • 结果如下

          static __attribute__((__aligned__( (16U)))) enet_rx_bd_struct_t eth0_rx_buffer_desc[6];
          static __attribute__((__aligned__( (16U)))) enet_tx_bd_struct_t eth0_tx_buffer_desc[1];
          static uint8_t __attribute__((__aligned__( (16U))))
              eth0_rx_buffer[6][(((unsigned long)(1518U) + ((unsigned long)((16U)) - 1)) &
                              ~((unsigned long)((16U)) - 1))];
          static uint8_t __attribute__((__aligned__( (16U))))
              eth0_tx_buffer[1][(((unsigned long)(1518U) + ((unsigned long)((16U)) - 1)) &
                              ~((unsigned long)((16U)) - 1))];
        
      • 准备config

          static const enet_buffer_config_t eth##n##_buffer_config = { 
              .rxBdNumber = CONFIG_ETH_MCUX_RX_BUFFERS, 
              .txBdNumber = CONFIG_ETH_MCUX_TX_BUFFERS, 
              .rxBuffSizeAlign = ETH_MCUX_BUFFER_SIZE, 
              .txBuffSizeAlign = ETH_MCUX_BUFFER_SIZE, 
              .rxBdStartAddrAlign = eth##n##_rx_buffer_desc, 
              .txBdStartAddrAlign = eth##n##_tx_buffer_desc, 
              .rxBufferAlign = eth##n##_rx_buffer[0], 
              .txBufferAlign = eth##n##_tx_buffer[0], 
              .rxMaintainEnable = true, 
              .txMaintainEnable = true, 
              ETH_MCUX_PTP_FRAMEINFO(n) 
          };				
        
      • 结果

          static const enet_buffer_config_t eth0_buffer_config = {
              .rxBdNumber = 6,
              .txBdNumber = 1,
              .rxBuffSizeAlign =
                  (((unsigned long)(1518U) + ((unsigned long)((16U)) - 1)) &
                  ~((unsigned long)((16U)) - 1)),
              .txBuffSizeAlign =
                  (((unsigned long)(1518U) + ((unsigned long)((16U)) - 1)) &
                  ~((unsigned long)((16U)) - 1)),
              .rxBdStartAddrAlign = eth0_rx_buffer_desc,
              .txBdStartAddrAlign = eth0_tx_buffer_desc,
              .rxBufferAlign = eth0_rx_buffer[0],
              .txBufferAlign = eth0_tx_buffer[0],
              .rxMaintainEnable = 1 ,
              .txMaintainEnable = 1 ,
              .txFrameInfo = ((void *)0) ,
          };
        
      • 关系

        enet2

      • 创建PM

          PM_DEVICE_DT_INST_DEFINE(n, eth_mcux_device_pm_action);
        
      • 结果

          sstatic const struct device *__pm_device__dts_ord_87_slot
              __attribute__((__section__(".z_pm_device_slots")));
        
          static struct pm_device __pm_device__dts_ord_87 = {
              .action_cb = eth_mcux_device_pm_action,
              .state = PM_DEVICE_STATE_ACTIVE,
              .flags = ((0 << PM_DEVICE_FLAG_WS_CAPABLE) | (0 << PM_DEVICE_FLAG_PD)),
              .domain = ((void *)0) ,
          };
        
    • 定义网络设备

        ETH_NET_DEVICE_DT_INST_DEFINE(n, 
                    eth_init, 
                    PM_DEVICE_DT_INST_GET(n), 
                    &eth##n##_context, 
                    &eth##n##_buffer_config, 
                    CONFIG_ETH_INIT_PRIORITY, 
                    &api_funcs, 
                    NET_ETH_MTU); 
      
      • 结果

          static struct device_state __devstate_dts_ord_87
              __attribute__((__section__(".z_devstate")));
        
          extern const device_handle_t __devicehdl_DT_N_S_soc_S_ethernet_400c0000[];
        
          const device_handle_t
              __attribute__((__weak__, __section__(".__device_handles_pass1")))
              __devicehdl_DT_N_S_soc_S_ethernet_400c0000[] = {
                  87, 6, 9, 10, 36, (-0x7fff - 1) , (-0x7fff - 1) , 88,
          };
        
          const struct device __device_dts_ord_87
              __attribute__((__section__(".z_device_" "POST_KERNEL" "80" "_"))) = {
                  .name = "ETH_0",
                  .config = (&eth0_buffer_config),
                  .api = (&api_funcs),
                  .state = (&__devstate_dts_ord_87),
                  .data = (&eth0_context),
                  .pm = (&__pm_device__dts_ord_87),
                  .handles = __devicehdl_DT_N_S_soc_S_ethernet_400c0000,
          };
        
          static const __attribute__((__aligned__(__alignof( struct init_entry))))
              struct init_entry __init___device_dts_ord_87
              __attribute__((__section__(".z_init_" "POST_KERNEL" "80" "_"))) = {
                  .init = (eth_init),
                  .dev = ((&__device_dts_ord_87)),
          };
        
          static struct ethernet_context _net_l2_data_dts_ord_870;
        
          static struct net_if_dev __net_if_dev_dts_ord_87_0
          __attribute__((section("._net_if_dev.static.__net_if_dev_dts_ord_87_0"))) 
          = {
              .dev = &(__device_dts_ord_87),
              .l2 = &(_net_l2_ETHERNET),
              .l2_data = &(_net_l2_data_dts_ord_870),
              .mtu = 1500,
          };
        
          static struct net_if __net_if_dts_ord_87_0[1]
              __attribute__((section("."_net_if.static.dts_ord_87"))) = {
                  [0 ...(1 - 1)] = {.if_dev = &(__net_if_dev_dts_ord_87_0),
                                  .config = { .ip = {}, }}
                  };
        
      • 关系

        enet3

      • 创建中断设备

          static void eth##n##_config_func(void) { 
              ETH_MCUX_IRQ(n, rx); 
              ETH_MCUX_IRQ(n, tx); 
              ETH_MCUX_IRQ(n, err); 
              ETH_MCUX_IRQ(n, common); 
              ETH_MCUX_IRQ_PTP(n); 
          } 
        
        • 结果

            static void eth0_config_func(void) {
            do {
                {
                static struct _isr_list __attribute__((section( ".intList"))) 
                    __isr_eth_mcux_rx_isr_irq_0 = {84, 0, (void *)&eth_mcux_rx_isr,
                                                    (const void *)(&__device_dts_ord_87)};
                z_arm_irq_priority_set(84, 0, 0);
                };
                arch_irq_enable(84);
            } while (0);
          
            do {
                {
                static struct _isr_list
                    __attribute__((section( ".intList"))) 
                    __isr_eth_mcux_tx_isr_irq_1 = {83, 0, (void *)&eth_mcux_tx_isr,
                                                    (const void *)(&__device_dts_ord_87)};
                z_arm_irq_priority_set(83, 0, 0);
                };
                arch_irq_enable(83);
            } while (0);
          
            do {
                static struct _isr_list
                    __attribute__((section( ".intList"))) 
                    __isr_eth_mcux_err_isr_irq_2 = {85, 0, (void *)&eth_mcux_err_isr,
                                                    (const void *)(&__device_dts_ord_87)};
                z_arm_irq_priority_set(85, 0, 0);
                arch_irq_enable(85);
            } while (0);
          
        • 关系

          enet4

网络设备初始化分析

static int eth_init(const struct device *dev) {

    struct eth_context *context = dev->data;

    int err;

    // 应用pinctrl的配置
    err = pinctrl_apply_state(context->pincfg, 0U);
    if (err) { return err; }

    // 从 pheriph 基地址找到inst编号,这里 inst = 0
    const uint32_t inst = ENET_GetInstance(context->base);

    context->clock = enet_clocks[inst];
    k_mutex_init(&context->rx_frame_buf_mutex);
    k_mutex_init(&context->tx_frame_buf_mutex);

    k_sem_init(&context->rx_thread_sem, 0, 6);
    k_sem_init(&context->tx_thread_sem, 0, 1);
    k_sem_init(&context->tx_buf_sem, 0, 1);
    // context->phy_work.handler = eth_mcux_phy_work
    k_work_init(&context->phy_work, eth_mcux_phy_work);
    k_work_init_delayable(&context->delayed_phy_work, eth_mcux_delayed_phy_work);

    /* Start interruption-poll thread */
    k_thread_create(&context->rx_thread, context->rx_thread_stack,
            K_KERNEL_STACK_SIZEOF(context->rx_thread_stack),
            eth_rx_thread, (void *) context, NULL, NULL,
            K_PRIO_COOP(2),
            0, K_NO_WAIT);
    k_thread_name_set(&context->rx_thread, "mcux_eth_rx");
    k_thread_create(&context->tx_thread, context->tx_thread_stack,
            K_KERNEL_STACK_SIZEOF(context->tx_thread_stack),
            eth_tx_thread, (void *) context, NULL, NULL,
            K_PRIO_COOP(3),
            0, K_NO_WAIT);
    k_thread_name_set(&context->tx_thread, "mcux_eth_tx");

    if (context->generate_mac) {
    context->generate_mac(context->mac_addr);
    }

    eth_mcux_init(dev);

    return 0;
}
  • eth_mcux_init(dev)

      static void eth_mcux_init(const struct device *dev) {
    
          struct eth_context *context = dev->data;
          const enet_buffer_config_t *buffer_config = dev->config;
          enet_config_t enet_config;
          uint32_t sys_clock;
          context->phy_state = eth_mcux_phy_state_initial;
          context->phy_handle->mdioHandle->ops = &enet_ops;
          context->phy_handle->ops = &phyksz8081_ops;
    
          sys_clock = CLOCK_GetFreq(kCLOCK_CoreSysClk);
    
          // 初始化 enet_config
          ENET_GetDefaultConfig(&enet_config);
          enet_config.interrupt |= kENET_RxFrameInterrupt;
          enet_config.interrupt |= kENET_TxFrameInterrupt;
          enet_config.interrupt |= kENET_MiiInterrupt;
          enet_config.miiMode = kENET_RmiiMode;
    
          // if (0)
          if ((IS_ENABLED(CONFIG_ETH_MCUX_PROMISCUOUS_MODE)) {
              enet_config.macSpecialConfig |= kENET_ControlPromiscuousEnable;
          }
    
          // if (0) 
          if (IS_ENABLED(CONFIG_NET_VLAN)) {
              enet_config.macSpecialConfig |= kENET_ControlVLANTagEnable;
          }
    
          // if (0) 
      	if (IS_ENABLED(CONFIG_ETH_MCUX_HW_ACCELERATION)) {
              enet_config.txAccelerConfig |=
                  kENET_TxAccelIpCheckEnabled | kENET_TxAccelProtoCheckEnabled;
              enet_config.rxAccelerConfig |=
                  kENET_RxAccelIpCheckEnabled | kENET_RxAccelProtoCheckEnabled;
          }
    
          // 初始化 ENET 模块:fsl_enet.c:320
          ENET_Init(context->base, &context->enet_handle, &enet_config, buffer_config,
                      context->mac_addr, sys_clock);
          ENET_SetSMI(context->base, sys_clock, 0);
    
          eth_mcux_phy_setup(context);
    
          ENET_SetCallback(&context->enet_handle, eth_callback, context);
    
          eth_mcux_phy_start(context);
      }