打印
[开发工具]

使用 PlatformIO 开发 APM32F4 系列 MCU 之二

[复制链接]
50|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wangqy_ic|  楼主 | 2024-10-11 11:06 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 wangqy_ic 于 2024-10-11 11:12 编辑

#申请原创#
使用 PlatformIO 开发 APM32F4 系列 MCU 之二 如何新增对 APM32F4 MCU 的支持

整个内容分为两部分:
第一部分先介绍怎样在 PlatformIO IDE 下 开发 APM32F4 MCU,见这里 https://bbs.21ic.com/icview-3406012-1-1.html。
第二部分介绍 APM32F4 MCU 相关的资源包如何整理编写,也就是本文了。

概述
上一篇文章《使用 PlatformIO 开发 APM32F4 系列 MCU》→ https://bbs.21ic.com/icview-3406012-1-1.html 介绍了搭建 PlatformIO 的开发环境。如果对这个开发环境熟悉的工程师会发现,https://registry.platformio.org/ 上是没有 APM32F4 MCU 的资源。这也是因为 PlatformIO 官方尚未支持这个系列的 MCU,半导体公司/热心网友也没有计划提交相应的内容。

这一篇文章就介绍怎样在这个平台下开发那些未得到官方支持的 MCU~

基础

基本概念

这一章节介绍一些基本概念,以便后续开发。我写的都是大白话,看官谅解~~~

  • platform 主要说明如何编译工程代码,所支持的 borad 等。
  • framwork 可以对应到 SDK。
  • tool 编译工具链等工具。
  • board 对应实际的开发板。

所需技能

要完成这个任务,至少需要以下这些知识技能:
  • 嵌入式开发相关知识 能懂的代码是怎样编译、链接、下载执行。熟练使用相关的工具(链)。
  • Python 编程知识 PlatformIO 是基于 Python 开发的,需要具备 Python 技能。
  • Scons 这是一个基于 Python 开发的构建工具,如果用过 RT-Thread 的话,应该就很熟悉了。

其他如 VSCode 的使用,就不再赘述了~

PlatformIO 资源简介

PlatformIO 运行所需的资源,默认是放在用户目录下 .platformio 文件夹内:`%USERPROFILE%\.platformio`。你可以把这个路径输入在文件管理器的地址栏,回车后就可直达~如果提示没有这个文件夹,可以先导航到 `%USERPROFILE%` 并在此创建 .platformio 文件夹。

.platformio 文件夹内,主要包含这两个文件夹:
  • packages
  • platforms

基本概念 一节的内容基本对应。

为了能支持一款 MCU 的开发,需要进行以下步骤:
  • 整理编写 platform 内容,放到 %USERPROFILE%\.platformio\platforms 文件夹内,下文描述用 `geehy_apm32` 代替。
  • 整理编写 framework 内容,放到 %USERPROFILE%\.platformio\packages 文件夹内,下文描述用 `framework-geehy-apm32f4` 代替。

重点步骤介绍

整理编写 platform: geehy_apm32

platform 文件夹必备的内容如下:
│  platform.json
│  platform.py

├─boards
│      apm32f407_tiny.json
│      apm32f411v_tiny.json

└─builder
   │  main.py
   │
   └─frameworks
           geehy_apm.py
           _bare.py

platform.json 文件

这个文件定义了本 platform 的基本信息,这些信息会在创建工程、编译代码,下载等任务期间使用。

内容如下:
{
  "name": "geehy_apm32",
  "title": "Geehy APM32",
  "description": "APM32 MCU",
  "homepage": "https://www.geehy.com/product/second/mcu",
  "license": "MIT",
  "keywords": [
    "dev-platform",
    "ARM",
    "Cortex-M",
    "Geehy",
    "APM32"
  ],
  "engines": {
    "platformio": "^5"
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/quincyzh/geehy-apm32-platform.git"
  },
  "version": "0.0.1",
  "frameworks": {
    "geehy-apm32": {
      "script": "builder/frameworks/geehy_apm.py",
      "description": "Geehy APM32 MCU SDK",
      "homepage": "https://www.geehy.com/",
      "title": "Geehy APM32 SDK"
    }
  },
  "packages": {
    "toolchain-gccarmnoneeabi": {
      "type": "toolchain",
      "owner": "platformio",
      "version": ">=1.60301.0,<1.80000.0",
      "optionalVersions": [
        "~1.60301.0",
        "~1.80201.0",
        "~1.90201.0",
        "~1.120301.0"
      ]
    },
    "framework-geehy-apm32f4": {
      "type": "framework",
      "owner": "quincy-zh",
      "optional": false
    },
    "tool-openocd": {
      "type": "uploader",
      "optional": true,
      "owner": "platformio",
      "version": "~3.1200.0"
    },
    "tool-jlink": {
      "type": "uploader",
      "optional": true,
      "owner": "platformio",
      "version": "^1.63208.0"
    }
  }
}
version 及其前面的,都是一些基本信息,也是字面含义,不再赘述。

关键的地方有两点:

1. frameworks 这个内容表明支持哪些 framework,以及对应的脚本。脚本主要用于说明编译准备(编译、链接参数,必备的文件等)。
2. packages 需要那些工具的支持。上面的内容对应了:
  - 使用 gccarmnoneeabi 编译工具链。
  - 使用 geehy-apm32f4 这个 framework 作为代码框架。
  - 支持 openocd 和 jlink 作为下载工具。

platform.py 文件

这个文件是 Python 脚本,里面定义了一个名为 `Geehy_apm32Platform` 的类,类名与 platform.json 里的 `name` 对应。
这个类配置、检查本 platform 所需的必备参数:
# Copyright 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys
import subprocess

from platformio.managers.platform import PlatformBase


IS_WINDOWS = sys.platform.startswith("win")


class Geehy_apm32Platform(PlatformBase):

    def configure_default_packages(self, variables, targets):
        board = variables.get("board")
        board_config = self.board_config(board)
        build_core = variables.get(
            "board_build.core", board_config.get("build.core", "arduino"))
        build_mcu = variables.get("board_build.mcu", board_config.get("build.mcu", ""))

        frameworks = variables.get("pioframework", [])
        
        if "geehy-apm32" in frameworks:
            assert build_mcu, ("Missing MCU field for %s" % board)
            device_package = "framework-geehy_%s" % build_mcu[0:7]
            self.frameworks["geehy-apm-sdk"] = {"package": device_package}

        #if any(f in frameworks for f in ("cmsis", "stm32cube")):
        #    self.packages["tool-ldscripts-ststm32"]["optional"] = False

        default_protocol = board_config.get("upload.protocol") or ""
        
        # configure J-LINK tool
        jlink_conds = [
            "jlink" in variables.get(option, "")
            for option in ("upload_protocol", "debug_tool")
        ]
        if board:
            jlink_conds.extend([
                "jlink" in board_config.get(key, "")
                for key in ("debug.default_tools", "upload.protocol")
            ])
        jlink_pkgname = "tool-jlink"
        if not any(jlink_conds) and jlink_pkgname in self.packages:
            del self.packages[jlink_pkgname]

        return PlatformBase.configure_default_packages(self, variables,
                                                       targets)

    def install_package(self, name, *args, **kwargs):
        pkg = super().install_package(name, *args, **kwargs)
        if name != "framework-zephyr":
            return pkg

        if not os.path.isfile(os.path.join(pkg.path, "_pio", "state.json")):
            self.pm.log.info("Installing Zephyr project dependencies...")
            try:
                subprocess.run([
                    os.path.normpath(sys.executable),
                    os.path.join(pkg.path, "scripts", "platformio", "install-deps.py"),
                    "--platform", self.name
                ])
            except subprocess.CalledProcessError:
                self.pm.log.info("Failed to install Zephyr dependencies!")

        return pkg

    def get_boards(self, id_=None):
        result = PlatformBase.get_boards(self, id_)
        if not result:
            return result
        if id_:
            return self._add_default_debug_tools(result)
        else:
            for key, value in result.items():
                result[key] = self._add_default_debug_tools(result[key])
        return result

    def _add_default_debug_tools(self, board):
        debug = board.manifest.get("debug", {})
        upload_protocols = board.manifest.get("upload", {}).get(
            "protocols", [])
        if "tools" not in debug:
            debug["tools"] = {}

        # J-Link, CMSIS-DAP
        for link in ("jlink", "cmsis-dap"):
            
            if link not in upload_protocols or link in debug["tools"]:
                continue
               
            if link == "jlink":
                assert debug.get("jlink_device"), (
                    "Missed J-Link Device ID for %s" % board.id)
                debug["tools"][link] = {
                    "server": {
                        "package": "tool-jlink",
                        "arguments": [
                            "-singlerun",
                            "-if", "SWD",
                            "-select", "USB",
                            "-device", debug.get("jlink_device"),
                            "-port", "2331"
                        ],
                        "executable": ("JLinkGDBServerCL.exe"
                                       if IS_WINDOWS else
                                       "JLinkGDBServer")
                    }
                }
            else:
                server_args = ["-s", "$PACKAGE_DIR/openocd/scripts"]
                if debug.get("openocd_board"):
                    server_args.extend([
                        "-f", "board/%s.cfg" % debug.get("openocd_board")
                    ])
                else:
                    assert debug.get("openocd_target"), (
                        "Missed target configuration for %s" % board.id)
                    server_args.extend([
                        "-f", "interface/%s.cfg" % link,
                        "-c", "transport select %s" % (
                            "hla_swd" if link == "stlink" else "swd"),
                        "-f", "target/%s.cfg" % debug.get("openocd_target")
                    ])
                    server_args.extend(debug.get("openocd_extra_args", []))

                debug["tools"][link] = {
                    "server": {
                        "package": "tool-openocd",
                        "executable": "bin/openocd",
                        "arguments": server_args
                    }
                }
            debug["tools"][link]["onboard"] = link in debug.get("onboard_tools", [])
            debug["tools"][link]["default"] = link in debug.get("default_tools", [])

        board.manifest["debug"] = debug
        return board

    def configure_debug_session(self, debug_config):
        if debug_config.speed:
            server_executable = (debug_config.server or {}).get("executable", "").lower()
            if "openocd" in server_executable:
                debug_config.server["arguments"].extend(
                    ["-c", "adapter speed %s" % debug_config.speed]
                )
            elif "jlink" in server_executable:
                debug_config.server["arguments"].extend(
                    ["-speed", debug_config.speed]
                )

boards 文件夹

这个文件夹下是本 platform 相关的开发板描述文件。开发板的描述文件只要是声明了一些关于开发板的基本信息,如 MCU 内核、型号,支持的framework、调试工具等。

boards\apm32f407_tiny.json 这个文件的内容:
{
  "build": {
    "core": "apm32",
    "cpu": "cortex-m4",
    "extra_flags": "-DAPM32F4 -DAPM32F40X",
    "f_cpu": "168000000L",
    "mcu": "apm32f407igt6",
    "product_line": "APM32F40x",
    "variant": "APM32F4xx"
  },
  "debug": {
    "default_tools": [
      "cmsis-dap"
    ],
    "jlink_device": "APM32F407IG",
    "openocd_target": "APM32F407IG",
    "svd_path": "APM32F40x.svd"
  },
  "frameworks": [
    "geehy-apm32"
  ],
  "name": "APM32F407 TINY",
  "upload": {
    "maximum_ram_size": 131072,
    "maximum_size": 1048576,
    "protocol": "cmsis-dap",
    "protocols": [
      "cmsis-dap",
      "jlink"
    ]
  },
  "url": "https://www.geehy.com/design/hardware_detail/32",
  "vendor": "Geehy"
}

builder 文件夹

这个文件夹下的内容是 Scons 的脚本(其实也是 Python 的脚本),描述了几项内容:
- 使用哪种编译工具链
- 编译参数
- 烧录/调试工具及对应参数
- 等等

这些脚本一起配合,才能完成编译、烧录、调试等工作。

文件内容较多,就不贴出来了。讲几个我觉得重要的:

builder\main.py 文件
编译工具链的选择:
env.Replace(
    AR="arm-none-eabi-gcc-ar",
    AS="arm-none-eabi-as",
    CC="arm-none-eabi-gcc",
    CXX="arm-none-eabi-g++",
    GDB="arm-none-eabi-gdb",
    OBJCOPY="arm-none-eabi-objcopy",
    RANLIB="arm-none-eabi-gcc-ranlib",
    SIZETOOL="arm-none-eabi-size",

    ARFLAGS=["rc"],

    SIZEPROGREGEXP=r"^(?:\.text|\.data|\.rodata|\.text.align|\.ARM.exidx)\s+(\d+).*",
    SIZEDATAREGEXP=r"^(?:\.data|\.bss|\.noinit)\s+(\d+).*",
    SIZECHECKCMD="$SIZETOOL -A -d $SOURCES",
    SIZEPRINTCMD='$SIZETOOL -B -d $SOURCES',

    PROGSUFFIX=".elf"
)

上面的代码片段,指定了使用 ARM-GCC 作为编译工具链,还写了几个正则表达式,用于获取编译后附件的尺寸信息。

下面的代码片段指示了烧录工具必须的参数:
upload_protocol = env.subst("$UPLOAD_PROTOCOL")
debug_tools = board.get("debug.tools", {})
upload_source = target_firm
upload_actions = []

if upload_protocol.startswith("jlink"):

    def _jlink_cmd_script(env, source):
        build_dir = env.subst("$BUILD_DIR")
        if not isdir(build_dir):
            makedirs(build_dir)
        script_path = join(build_dir, "upload.jlink")
        commands = [
            "h",
            "loadbin %s, %s" % (source, board.get(
                "upload.offset_address", "0x08000000")),
            "r",
            "q"
        ]
        with open(script_path, "w") as fp:
            fp.write("\n".join(commands))
        return script_path

    env.Replace(
        __jlink_cmd_script=_jlink_cmd_script,
        UPLOADER="JLink.exe" if system() == "Windows" else "JLinkExe",
        UPLOADERFLAGS=[
            "-device", board.get("debug", {}).get("jlink_device"),
            "-speed", env.GetProjectOption("debug_speed", "4000"),
            "-if", ("jtag" if upload_protocol == "jlink-jtag" else "swd"),
            "-autoconnect", "1",
            "-NoGui", "1"
        ],
        UPLOADCMD='$UPLOADER $UPLOADERFLAGS -CommanderScript "${__jlink_cmd_script(__env__, SOURCE)}"'
    )
    upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")]

builder\frameworks\geehy_apm.py 文件
这个文件指示了 framework_geehy_apm (可以理解为 SDK)里头文件的目录,哪些文件需要编译~例如:
sources_path = os.path.join(FRAMEWORK_DIR, "Libraries", "Device", "Geehy", "APM32F4xx", "Source")
prepare_startup_file(sources_path)

env.BuildSources(
    os.path.join("$BUILD_DIR", "FrameworkCMSIS"),
    sources_path,
    src_filter=[
        "-<*>",
        "+<system_%sxx.c>" % mcu[0:7],
        "+<gcc/startup_%s.S>" % PRODUCT_LINE.lower(),
    ]
)

这段代码指示了 APM32F4xx SKD 里关于 CMSIS 部分需要加入编译的代码~

整理编写 framework: framework-geehy-apm32f4

前面提到,这个 framework 相当于厂商提供的 SDK。编写整理改部分内容所需要做的就是声明一些基本信息就可以。具体的编译参数,需要编译的文件,头文件的路径等信息,都在 `builder\frameworks\geehy_apm.py` 文件里有说明。


这个文件夹下除了 SDK 部分还需要两个文件:
  • .piopm
  • package.json

.piopm 文件

这个文件其实也是 JSON 格式,内容如下:
{
   "type": "tool",
   "name": "framework-geehy-apm32f4",
   "version": "0.0.1",
   "spec": {
      "owner": "quincy-zh",
      "id": "",
      "name": "framework-geehy-apm32f4",
      "requirements": null,
      "uri": null
   }
}

关于这个文件有个注意事项:platform.json 文件中指定这个 framework 的信息要与这里的信息一致,否则编译是会报错说找不到 framework:



package.json 文件

这个文件也是说明本 framework 的信息,与上面那个文件的内容差不多,不知道是不是后续 PlatformIO 版本会淘汰这个~
{
  "description": "PIO framework for Geehy APM32F4",
  "keywords": [
    "framework",
    "hal",
    "apm32",
    "geehy"
  ],
  "name": " framework-geehy-apm32f4",
  "version": "0.0.1",
  "homepage": "https://github.com/Quincy-Zh/framework-geehy-apm32f4.git"
}

总结

利用 PlatformIO 官方提供的内容,很容易给那些官方不支持的 MCU 提供开发包。本文只是简单地介绍了一个起手式,还有很多地方不完善,使用中需要注意~~~

最后一个简短的声明:
1. 内容是基于 PlatformIO 官方的相关文件修改而来,可能有一些未使用到的内容,但是不影响使用^-^
2. 本文描述的内容仅供学习参考,如果使用与于正式产品,请自行评估安全性、合法性以及相关知识产权风险。
3. 本文使用 SDK 是修改自官方提供的 SDK,非 Geehy 官方作品。



使用特权

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

本版积分规则

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

16

主题

87

帖子

4

粉丝