返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

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

[复制链接]
 楼主| 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 文件夹必备的内容如下:
  1. │  platform.json
  2. │  platform.py

  3. ├─boards
  4. │      apm32f407_tiny.json
  5. │      apm32f411v_tiny.json

  6. └─builder
  7.    │  main.py
  8.    │
  9.    └─frameworks
  10.            geehy_apm.py
  11.            _bare.py

platform.json 文件

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

内容如下:
  1. {
  2.   "name": "geehy_apm32",
  3.   "title": "Geehy APM32",
  4.   "description": "APM32 MCU",
  5.   "homepage": "https://www.geehy.com/product/second/mcu",
  6.   "license": "MIT",
  7.   "keywords": [
  8.     "dev-platform",
  9.     "ARM",
  10.     "Cortex-M",
  11.     "Geehy",
  12.     "APM32"
  13.   ],
  14.   "engines": {
  15.     "platformio": "^5"
  16.   },
  17.   "repository": {
  18.     "type": "git",
  19.     "url": "https://gitee.com/quincyzh/geehy-apm32-platform.git"
  20.   },
  21.   "version": "0.0.1",
  22.   "frameworks": {
  23.     "geehy-apm32": {
  24.       "script": "builder/frameworks/geehy_apm.py",
  25.       "description": "Geehy APM32 MCU SDK",
  26.       "homepage": "https://www.geehy.com/",
  27.       "title": "Geehy APM32 SDK"
  28.     }
  29.   },
  30.   "packages": {
  31.     "toolchain-gccarmnoneeabi": {
  32.       "type": "toolchain",
  33.       "owner": "platformio",
  34.       "version": ">=1.60301.0,<1.80000.0",
  35.       "optionalVersions": [
  36.         "~1.60301.0",
  37.         "~1.80201.0",
  38.         "~1.90201.0",
  39.         "~1.120301.0"
  40.       ]
  41.     },
  42.     "framework-geehy-apm32f4": {
  43.       "type": "framework",
  44.       "owner": "quincy-zh",
  45.       "optional": false
  46.     },
  47.     "tool-openocd": {
  48.       "type": "uploader",
  49.       "optional": true,
  50.       "owner": "platformio",
  51.       "version": "~3.1200.0"
  52.     },
  53.     "tool-jlink": {
  54.       "type": "uploader",
  55.       "optional": true,
  56.       "owner": "platformio",
  57.       "version": "^1.63208.0"
  58.     }
  59.   }
  60. }
version 及其前面的,都是一些基本信息,也是字面含义,不再赘述。

关键的地方有两点:

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

platform.py 文件

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

  14. import os
  15. import sys
  16. import subprocess

  17. from platformio.managers.platform import PlatformBase


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


  19. class Geehy_apm32Platform(PlatformBase):

  20.     def configure_default_packages(self, variables, targets):
  21.         board = variables.get("board")
  22.         board_config = self.board_config(board)
  23.         build_core = variables.get(
  24.             "board_build.core", board_config.get("build.core", "arduino"))
  25.         build_mcu = variables.get("board_build.mcu", board_config.get("build.mcu", ""))

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

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

  34.         default_protocol = board_config.get("upload.protocol") or ""
  35.         
  36.         # configure J-LINK tool
  37.         jlink_conds = [
  38.             "jlink" in variables.get(option, "")
  39.             for option in ("upload_protocol", "debug_tool")
  40.         ]
  41.         if board:
  42.             jlink_conds.extend([
  43.                 "jlink" in board_config.get(key, "")
  44.                 for key in ("debug.default_tools", "upload.protocol")
  45.             ])
  46.         jlink_pkgname = "tool-jlink"
  47.         if not any(jlink_conds) and jlink_pkgname in self.packages:
  48.             del self.packages[jlink_pkgname]

  49.         return PlatformBase.configure_default_packages(self, variables,
  50.                                                        targets)

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

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

  65.         return pkg

  66.     def get_boards(self, id_=None):
  67.         result = PlatformBase.get_boards(self, id_)
  68.         if not result:
  69.             return result
  70.         if id_:
  71.             return self._add_default_debug_tools(result)
  72.         else:
  73.             for key, value in result.items():
  74.                 result[key] = self._add_default_debug_tools(result[key])
  75.         return result

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

  82.         # J-Link, CMSIS-DAP
  83.         for link in ("jlink", "cmsis-dap"):
  84.             
  85.             if link not in upload_protocols or link in debug["tools"]:
  86.                 continue
  87.                
  88.             if link == "jlink":
  89.                 assert debug.get("jlink_device"), (
  90.                     "Missed J-Link Device ID for %s" % board.id)
  91.                 debug["tools"][link] = {
  92.                     "server": {
  93.                         "package": "tool-jlink",
  94.                         "arguments": [
  95.                             "-singlerun",
  96.                             "-if", "SWD",
  97.                             "-select", "USB",
  98.                             "-device", debug.get("jlink_device"),
  99.                             "-port", "2331"
  100.                         ],
  101.                         "executable": ("JLinkGDBServerCL.exe"
  102.                                        if IS_WINDOWS else
  103.                                        "JLinkGDBServer")
  104.                     }
  105.                 }
  106.             else:
  107.                 server_args = ["-s", "$PACKAGE_DIR/openocd/scripts"]
  108.                 if debug.get("openocd_board"):
  109.                     server_args.extend([
  110.                         "-f", "board/%s.cfg" % debug.get("openocd_board")
  111.                     ])
  112.                 else:
  113.                     assert debug.get("openocd_target"), (
  114.                         "Missed target configuration for %s" % board.id)
  115.                     server_args.extend([
  116.                         "-f", "interface/%s.cfg" % link,
  117.                         "-c", "transport select %s" % (
  118.                             "hla_swd" if link == "stlink" else "swd"),
  119.                         "-f", "target/%s.cfg" % debug.get("openocd_target")
  120.                     ])
  121.                     server_args.extend(debug.get("openocd_extra_args", []))

  122.                 debug["tools"][link] = {
  123.                     "server": {
  124.                         "package": "tool-openocd",
  125.                         "executable": "bin/openocd",
  126.                         "arguments": server_args
  127.                     }
  128.                 }
  129.             debug["tools"][link]["onboard"] = link in debug.get("onboard_tools", [])
  130.             debug["tools"][link]["default"] = link in debug.get("default_tools", [])

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

  133.     def configure_debug_session(self, debug_config):
  134.         if debug_config.speed:
  135.             server_executable = (debug_config.server or {}).get("executable", "").lower()
  136.             if "openocd" in server_executable:
  137.                 debug_config.server["arguments"].extend(
  138.                     ["-c", "adapter speed %s" % debug_config.speed]
  139.                 )
  140.             elif "jlink" in server_executable:
  141.                 debug_config.server["arguments"].extend(
  142.                     ["-speed", debug_config.speed]
  143.                 )

boards 文件夹

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

boards\apm32f407_tiny.json 这个文件的内容:
  1. {
  2.   "build": {
  3.     "core": "apm32",
  4.     "cpu": "cortex-m4",
  5.     "extra_flags": "-DAPM32F4 -DAPM32F40X",
  6.     "f_cpu": "168000000L",
  7.     "mcu": "apm32f407igt6",
  8.     "product_line": "APM32F40x",
  9.     "variant": "APM32F4xx"
  10.   },
  11.   "debug": {
  12.     "default_tools": [
  13.       "cmsis-dap"
  14.     ],
  15.     "jlink_device": "APM32F407IG",
  16.     "openocd_target": "APM32F407IG",
  17.     "svd_path": "APM32F40x.svd"
  18.   },
  19.   "frameworks": [
  20.     "geehy-apm32"
  21.   ],
  22.   "name": "APM32F407 TINY",
  23.   "upload": {
  24.     "maximum_ram_size": 131072,
  25.     "maximum_size": 1048576,
  26.     "protocol": "cmsis-dap",
  27.     "protocols": [
  28.       "cmsis-dap",
  29.       "jlink"
  30.     ]
  31.   },
  32.   "url": "https://www.geehy.com/design/hardware_detail/32",
  33.   "vendor": "Geehy"
  34. }

builder 文件夹

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

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

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

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

  10.     ARFLAGS=["rc"],

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

  15.     PROGSUFFIX=".elf"
  16. )

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

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

  5. if upload_protocol.startswith("jlink"):

  6.     def _jlink_cmd_script(env, source):
  7.         build_dir = env.subst("$BUILD_DIR")
  8.         if not isdir(build_dir):
  9.             makedirs(build_dir)
  10.         script_path = join(build_dir, "upload.jlink")
  11.         commands = [
  12.             "h",
  13.             "loadbin %s, %s" % (source, board.get(
  14.                 "upload.offset_address", "0x08000000")),
  15.             "r",
  16.             "q"
  17.         ]
  18.         with open(script_path, "w") as fp:
  19.             fp.write("\n".join(commands))
  20.         return script_path

  21.     env.Replace(
  22.         __jlink_cmd_script=_jlink_cmd_script,
  23.         UPLOADER="JLink.exe" if system() == "Windows" else "JLinkExe",
  24.         UPLOADERFLAGS=[
  25.             "-device", board.get("debug", {}).get("jlink_device"),
  26.             "-speed", env.GetProjectOption("debug_speed", "4000"),
  27.             "-if", ("jtag" if upload_protocol == "jlink-jtag" else "swd"),
  28.             "-autoconnect", "1",
  29.             "-NoGui", "1"
  30.         ],
  31.         UPLOADCMD='$UPLOADER $UPLOADERFLAGS -CommanderScript "${__jlink_cmd_script(__env__, SOURCE)}"'
  32.     )
  33.     upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")]

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

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

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

整理编写 framework: framework-geehy-apm32f4

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

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

.piopm 文件

这个文件其实也是 JSON 格式,内容如下:
  1. {
  2.    "type": "tool",
  3.    "name": "framework-geehy-apm32f4",
  4.    "version": "0.0.1",
  5.    "spec": {
  6.       "owner": "quincy-zh",
  7.       "id": "",
  8.       "name": "framework-geehy-apm32f4",
  9.       "requirements": null,
  10.       "uri": null
  11.    }
  12. }

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

f5ab2773cc748b563b0cffbb380949ab

package.json 文件

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

总结

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

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



打赏榜单

21小跑堂 打赏了 50.00 元 2024-10-31
理由:恭喜通过原创审核!期待你更多的原创作品~

评论

书接上回,在环境搭建完成之后,继续添加APM32F4 的资源包支持,重点步骤介绍详细,实现思路清晰  发表于 2024-10-31 17:15
xionghaoyun 发表于 2024-11-6 09:05 | 显示全部楼层
py语言看不懂
 楼主| wangqy_ic 发表于 2024-11-7 12:25 | 显示全部楼层

直接用~有问题大家一起解决~

评论

看惯了c语言 觉得py好怪  发表于 2024-11-7 13:39
[鑫森淼焱垚] 发表于 2024-11-7 15:01 | 显示全部楼层
好文,点赞
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

22

主题

117

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部
个人签名:感恩的心对人。

22

主题

117

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部