PlatformIOをカスタム環境で使う


PlatformIOとは

PlatformIOは,VSCodeやCLion等の拡張機能として,またスタンドアロンのCLIとして利用できるクロス開発環境群です.クロスコンパイラ,各種組み込みフレームワークのダウンロード・設定・利用をIDE問わず簡単に行えます.Arduinoやmbedというような主要な環境(基板の定義やフレームワーク・クロスコンパイラ)はデフォルトでサポートされています.
そんなPlatformIOでは,(一種のビルドシステムを提供する以上)当然PlatformIOが想定しない環境を使うこともできます.今回は,とある大学の授業で取り扱った環境をベースに,その手法を解説します.

例示する環境

  • TI製LaunchPad評価ボード EK-TM4C123GXL
    • 公式である程度の環境定義はありますが,それを拡張する形で実装しました
  • TivaWare(HAL: ハードウェア抽象化ライブラリ) の独自バージョン

この記事で対象としないもの

  • PlatformIOでサポートされていない新しい種類のCPUを使う場合: ビルドスクリプト等の知識は再利用できます
  • 既存フレームワーク(mbed)などを使っている場合: 別記事に書きます

カスタム環境を作ろう

これから作るもの

  • 対象となる基板の定義: 動作周波数やCPU種別を示します
  • ビルドスクリプト: カスタムライブラリ(HAL等)を読み込ませるために必要です.今回はextra_scriptsとして読み込ませました
  • platformio.ini: PlatformIOのプロジェクト設定で,ビルドスクリプトを指定します
  • スタートアップコード: 0x00番地に配置される割り込みベクタテーブルや,ハードウェア初期化コードを含むものです

基板定義の作成

公式ドキュメント: Custom Embedded Boardsに従って作りました.
PlatformIOのプロジェクトのディレクトリ以下にboards/(基板名).jsonを作ります.
ここで定義するべき値は,CPUアーキテクチャやフレームワーク毎にことなり,ドキュメンテーションされていないこともあるので,公式GitHubにあるレポジトリを確認するほうがいいでしょう.
今回の場合はplatform-titivaを参照します.レポジトリ自体に定義のJSONがいくつかあるため,それを参考にするか,ビルドスクリプト(builder/以下)を確認します.

{
    "build": {
        "core": "tivac",
        "cpu": "cortex-m4",
        "f_cpu": "80000000L",
        "mcu": "lptm4c1230c3pm",
        "variant": "EK-TM4C123GXL"
    },
    "debug": {
        "svd_path": "TM4C1230C3PM.svd",
        "tools": {
            "ti-icdi": {
                "onboard": true,
                "server": {
                    "arguments": [
                        "-s",
                        "$PACKAGE_DIR/scripts",
                        "-f",
                        "board/ti_ek-tm4c123gxl.cfg"
                    ],
                    "executable": "bin/openocd",
                    "package": "tool-openocd"
                }
            }
        }
    },
    "frameworks": [],
    "name": "EK-TM4C123GXL",
    "upload": {
        "maximum_ram_size": 32768,
        "maximum_size": 262144
    },
    "url": "http://www.ti.com/ww/en/launchpad/launchpads-connected-ek-tm4c123gxl.html",
    "vendor": "TI"
}

build以下ではボードのCPU種別や周波数を定義しています.フレームワークを用いない場合のビルドスクリプト内では,f_cpu,cpuのみが参照されているようです.
debug以下ではデバッガに関する設定をしています.svd_pathはCPU内のメモリ構造などが格納されているものです.ファイル自体は先ほどのレポジトリに格納されています.toolsは,デバッガツールを指定しています.今回はよくあるOpenOCDを用いる設定です.ここでの設定は,先ほどのレポジトリのビルドスクリプトでいくつか使用されるほか,PlatformIO内部でも使用されています.
frameworksは,利用できるフレームワークを一覧(string[])で定義しています.今回は,独自のHALのみを用いるので,指定しません.
uploadは,RAMとROMの最大値を定義しています.

ビルドスクリプト

PlatformIOでは,PythonをベースとしたビルドシステムSConsが用いられています.このビルドシステムにビルドスクリプトを指定する形で介入できるようになっているので,それを使ってHALの読み込みました.

今回はこのようなスクリプトを用いています.

from os.path import isdir, join
Import("env")


TIVAWARE_DIR = join(env['PROJECT_DIR'], "../../../../ti/TivaWare_C_Series-1.0") # HAL directory
assert isdir(TIVAWARE_DIR)

env.Append(
    CPPDEFINES=[
        ("PART_TM4C123GH6PM", ""), # define for HAL
        ("PART_TM4C123GE6PM", ""),
        ("TARGET_IS_BLIZZARD_RB1", ""),
        ("gcc", ""),
    ],

    CCFLAGS=[
        "-mfloat-abi=hard",
        "-mfpu=fpv4-sp-d16",
        "-mabi=aapcs",
        "--param", "max-inline-insns-single=500",
        "-nostdlib"
    ],

    LINKFLAGS=[
        "-Wl,--entry=ResetISR",
        "-Wl,--check-sections",
        "-Wl,--gc-sections",
        "-Wl,--unresolved-symbols=report-all",
        "-Wl,--warn-common",
        "-Wl,--warn-section-align",
        "-Wl,-Tscripts/linker.ld", # linker script
        "-mfloat-abi=hard",
        "-mfpu=fpv4-sp-d16",
        "-fsingle-precision-constant"
    ],
    LIBS=["libc", "driverlib"],
    CPPPATH=[
        TIVAWARE_DIR,
        join(TIVAWARE_DIR, "inc"),
        join(TIVAWARE_DIR, "driverlib"),
        join(TIVAWARE_DIR, "utils"),
    ],
    LIBSOURCE_DIRS=[
        join(TIVAWARE_DIR, "driverlib")
    ]
)

libs = []
libs.append(
    env.BuildLibrary(
        join("$BUILD_DIR", "driverlib"), # library destination
        join(TIVAWARE_DIR, "driverlib"))) # library source

env.Prepend(LIBS=libs)

Import("env")は,Pythonのimport文ではなく,PlatformIO固有のものです.envという変数がアクセス可能になります.env.Appendで,ビルドシステムの変数に値を加えています.

  • CPPDEFINES: C++のdefine一覧
  • CCFLAGS: C++コンパイラの引数
  • LINKFLAGS: リンカ―の引数.-Wl,-Tscripts/linker.ldでリンカスクリプト(後述)を指定しています.
  • LIBS: コンパイラに渡すライブラリの一覧(-l引数を使用)
  • CPPPATH: インクルードディレクトリ
  • LIBSOURCE_DIRS: ライブラリのソース
    ここでの変数は,SConsのConstruction Variablesなどに示されています.

env.BuildLibraryは,HALをライブラリとしてビルドするための指定です.
この関数は,PlatformIOが独自に実装しているものです.platformio.pyなどを見れば,SConsのStaticLibrary相当であると分かります.

platformio.ini

このファイルには,対象の基板やビルドスクリプト,環境名をini形式で記載します.環境名はplatform-titivaを使うため,titivaとします.
ビルドスクリプトもここで指定します.
今回はこのようになりました.

[env:tm4c123gxlevk]
platform = titiva
board = tm4c123gxl
extra_scripts = pre:scripts/build.py
monitor_speed = 9600

セクション名env:lptm4c1230c3pmは,使う環境を複数定義するときに使います.こうした形で複数のセクションを定義できます.
boardboards/以下で作ったJSONファイルです.そして,extra_scriptsで,ビルド前(pre)にビルドスクリプトを読み込ませています.
monitor_speedは,シリアルモニタのbaud rate指定です.

スタートアップコード

今回はHALに付属するexampleから拝借しました.C言語やアセンブラで書かれ,割り込みベクタテーブルと,メインルーチン前の初期化コードを含みます

リンカスクリプト

同じくHALに付属するexampleから拝借しました.どのアドレスにコードを配置するかを示すファイルです.

あとがき

そのほかに,mbedをフレームワークとして用いる場合や,PlatformIOのライブラリシステムを活用することも行いました.それらについては,また別の機会に書きます.