[开发工具]使用 PlatformIO 开发 APM32F4 系列 MCU 之二

使用 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 官方作品。
---------------------
作者:wangqy_ic
链接:https://bbs.21ic.com/icview-3406612-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值