本帖最后由 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 文件夹内,主要包含这两个文件夹:
与 基本概念 一节的内容基本对应。
为了能支持一款 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 文件
这个文件其实也是 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 官方作品。
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
打赏榜单
21小跑堂 打赏了 50.00 元 2024-10-31 理由:恭喜通过原创审核!期待你更多的原创作品~
|
书接上回,在环境搭建完成之后,继续添加APM32F4 的资源包支持,重点步骤介绍详细,实现思路清晰