iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 18
0
IoT

拿到錘子的我想在微控制器上面執行 Ruby系列 第 18

Day 18 - PlatformIO 的準備

大多數時候我們使用 Arduino 這類開發板的時候都會使用 Arduino 提供的 IDE或者像是 ESP32/ESP8266 官方提供的開發工具,不過當有一些相依的套件需要使用的時候,就不一定是那麼的方便。而 PlatformIO 彙整了大部分開發板的工具,並且提供相依套件管理跟編譯工具的自動化等處理,讓使用上變得非常容易,也很適合我們這次的開發跟使用。

PlatformIO 環境

PlatformIO 有兩種版本,一種是 IDE 的版本可以跟 VSCode 整合,另外一種是 CLI(Command Line Interface)的版本,文章會以 macOS 上使用 CLI 版本當範例說明,大家可以依照自身開發環境的狀況選擇偏好的做法。

在寫鐵人賽的時候 PlatformIO 已經更新到 5.0 和舊版有一些差異,文章都會用 5.0 的設定說明。

專案結構

目前我們有自己的 Makefile 來輔助編譯之外,還有 src 目錄放置原始碼,在 PlatformIO 的專案結構下我們需要將 src 放到 lib/iron 下面作為 Library 使用,然後將 src/main.c 放到 PlatformIO 的 src/main.cpp 當作是 Arduino 的主程式使用。

初始化專案

在原本的專案中使用 pio init -b d1 這邊的 -b 是表示使用 d1 這個開發板的意思,可以用 pio boards 尋找自己手邊的有開發板來使用,我們這次是以旗標的復古遊戲機為基礎,要達到相同的條件只需要支援 Arduino 框架以及有一塊適合的 TFT 螢幕即可。

執行完畢後專案大致上會有這樣的結構

├── Makefile
├── app.rb
├── bin
├── include
│   └── README
├── lib
│   └── README
├── platformio.ini
├── src
│   ├── app.h
│   ├── irep.c
│   ├── irep.h
│   ├── iron.c
│   ├── iron.h
│   ├── khash.h
│   ├── main.c
│   ├── opcode.h
│   ├── utils.h
│   ├── value.h
│   ├── vm.c
│   └── vm.h
└── test
    └── README

我們先用 rm -rf bin Makefile 把之後會由 PlatformIO 替代的部分刪除,接著用 mkdir -p lib/iron 製作放置我們 Ruby VM 實作的目錄,然後 mv src/* lib/iron/ 將檔案先移動過去,最後用 mv lib/iron/main.c src/main.cpp 把主程序移動回來。

完成後專案的結構大致上會像下面這樣

├── app.rb
├── include
│   └── README
├── lib
│   ├── README
│   └── iron
│       ├── app.h
│       ├── irep.c
│       ├── irep.h
│       ├── iron.c
│       ├── iron.h
│       ├── khash.h
│       ├── opcode.h
│       ├── utils.h
│       ├── value.h
│       ├── vm.c
│       └── vm.h
├── platformio.ini
├── src
│   └── main.cpp
└── test
    └── README

PlatformIO 配置

接下來我們需要讓 PlatformIO 可以繼續正常在電腦上做測試,因此我們會利用 test (測試)的功能在電腦上製作一個可以運行的測試檔案,新增檔案 test/test_native/main.cpp 來撰寫最初的測試

// test/native.cpp

#include <stdio.h>
#include <unity.h>

#include "app.h"
#include "iron.h"
#include "vm.h"

void test_mrb_run(void) {
  mrb_state* mrb = mrb_open();
  mrb_run(mrb, app);
  mrb_close(mrb);
}

int main(int argc, char **argv) {
  UNITY_BEGIN();
  RUN_TEST(test_mrb_run);
  UNITY_END();
  return 0;
}

基本上跟我們在之前的 src/main.c 沒有太多差異,但是配合 PlatformIO 測試框架進行了簡單的改寫,要注意的是這個並不是一個標準的測試,只是方便我們驗證 Ruby VM 能夠正常運作的手段。

接下來我們需要能讓 PlatformIO 在編譯前幫我們把機器碼產生,為了方便處理我們會直接生成 app.h 放到 include/ 目錄下面,因此先用 rm lib/iron/app.h 把之前用來定義 uint8_t app[] 的檔案刪除,然後加入新的 Python 腳本(pre_build_ruby.py)用於 PlatformIO 的預處理。

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

Import("env")

config = configparser.ConfigParser()
config.read("platformio.ini")

script = config.get(f"env:{env['PIOENV']}", "custom_ruby_script")

env.Execute(f"mrbc -B app -o include/app.h {script}")

我們簡單的利用 PlatformIO 的 API 讀取了設定中的 custom_ruby_script 當作參考,決定要以哪個檔案為基礎生成機器碼,這樣也能在正式放到開發板上的檔案做區分方便測試。

接下來要修改 platformio.ini 的設定,將我們剛剛的預處理設定加入,以及增加 native 環境讓我們可以在電腦上做測試。

; platformio.ini

[env]
platform = native
extra_scripts =
  pre_build_ruby.py

[env:test]
build_type = debug
build_flags = -D DEBUG
custom_ruby_script = app.rb

[env:d1]
platform = espressif8266
board = d1
framework = arduino
custom_ruby_script = app.rb
test_ignore = test_*

我們先將預設環境設定為 native 並且指定 d1 環境跳過測試,讓我們只針對本機測試,這樣就能製作出一個類似我們原本搭設的開發環境,也方便繼續擴充功能。

不過直接運行 pio test 還是會發生問題,這是因為我們原本都是以 C 語言作為前提所撰寫的,現在改為 C++ 之後需要做一些處理,目前只需要在 lib/iron/iron.hlib/iron/vm.h 兩個檔案加入針對 C++ 的設定即可。

// lib/iron/iron.h

#ifndef _IRON_H_
#define _IRON_H_

#include "khash.h"
#include "value.h"

#define IRON_API extern

#ifdef __cplusplus
extern "C" {
#endif

// ...

#ifdef __cplusplus
}
#endif

#endif
// lib/iron/vm.h

#ifndef _IRON_VM_H_
#define _IRON_VM_H_

#include <stdint.h>
#include "irep.h"
#include "iron.h"

// ...

#ifdef __cplusplus
extern "C" {
#endif

// ...

#ifdef __cplusplus
}
#endif

#endif

這邊我們針對 C++ 加入了 extern "C" 的設定讓 C++ 可以順利存取到這些在 C 定義的方法,接著運行 pio test 就可以看到我們的測試運行起來,並且將除錯訊息印出來。

Collected 1 items

Processing test_native in test environment
----------------------------------------------------------------------------------------------------------------------
Building...
Testing...
[DEBUG] locals: 1, regs: 6, ireps: 0
[DEBUG] r[1] = self 0
[DEBUG] r[2] = 10
[DEBUG] r[2] = r[2] + 5
[DEBUG] r[3] = mrb_int(2)
[DEBUG] r[4] = mrb_int(3)
[DEBUG] func puts not found
[DEBUG] return r[1]
test/test_native/main.cpp:16:test_mrb_run       [PASSED]

-----------------------
1 Tests 0 Failures 0 Ignored
OK
============================================= [PASSED] Took 0.84 seconds =============================================

Test         Environment    Status    Duration
-----------  -------------  --------  ------------
test_native  test           PASSED    00:00:00.842
test_native  d1             IGNORED
============================================ 1 succeeded in 00:00:00.842 ============================================

現階段運行 pio run 會發現無法正常在 d1 的環境編譯,下一篇我們會對原始碼做一些修正讓他可以順利編譯給 D1 Mini 這塊開發板。


上一篇
Day 17 - 變數(二)
下一篇
Day 19 - 把程式碼上傳到開發板
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言