雖然我們已經可以使用 pio test
對我們撰寫的 Ruby VM 進行測試,但是使用 pio run
編譯的時候還是會出現無法正常運作的錯誤,我們需要對原始碼進行一些修正並且上傳到開發板上來進行驗證。
在 Arduino 的框架下,我們會需要 setup
和 loop
兩個方法才能正常使用,我們先將 src/main.cpp
修正成正確的結構。
// src/main.cpp
/**
* iron-ruby-vm
*/
#include <Arduino.h>
#include <stdio.h>
#include "app.h"
#include "vm.h"
#include "iron.h"
void mrb_puts(mrb_state* mrb) {
int argc = mrb_get_argc(mrb);
mrb_value* argv = mrb_get_argv(mrb);
for(int i = 0; i < argc; i++) {
printf("%d\n", mrb_fixnum(argv[i]));
}
}
void setup() {
mrb_state* mrb = mrb_open();
mrb_define_method(mrb, "puts", mrb_puts);
mrb_run(mrb, app);
mrb_close(mrb);
}
void loop() {
// 不做事
}
不過修改為這個版本後,運行 pio run
除了有一些警告訊息我們需要處理外,會發現用來測試的 native
環境也因為沒有 Arduino.h
和 int main()
而發生錯誤,目前 PlatformIO 似乎沒有自動處理的方法,但我們還是可以利用 build_flags
選項來做簡單的處理。
修改 platformio.ini
的設定,讓開發版增加 -DESP8266
的選項。
; platformio.ini
; ...
[env:d1]
platform = espressif8266
board = d1
framework = arduino
custom_ruby_script = app.rb
build_flags = -D ESP8266
test_ignore = test_*
然後重新對我們的 src/main.c
調整,利用 #if defined(ESP8266)
來判斷要引用的檔案
/**
* iron-ruby-vm
*/
#if defined(ESP8266)
#include <Arduino.h>
#endif
#include <stdio.h>
#include "app.h"
#include "vm.h"
#include "iron.h"
void mrb_puts(mrb_state* mrb) {
int argc = mrb_get_argc(mrb);
mrb_value* argv = mrb_get_argv(mrb);
for(int i = 0; i < argc; i++) {
printf("%d\n", mrb_fixnum(argv[i]));
}
}
void run_vm() {
mrb_state* mrb = mrb_open();
mrb_define_method(mrb, "puts", mrb_puts);
mrb_run(mrb, app);
mrb_close(mrb);
}
#if defined(ESP8266)
void setup() {
run_vm();
}
void loop() {
// 不做事
}
#else
int main(int argc, char** argv) {
run_vm();
}
#endif
我們增加了 run_vm
方法來重複使用相同的程式碼片段,如此一來就能夠讓我們編譯出來的程式碼同時在電腦跟開發板上運作。
接下來我們把一些編譯過程中出現的警告清理一下,之前在測試的時候因為沒有開啟相關的功能因此有一些多餘的程式碼被實作進去。
// lib/iron/vm.c
int mrb_exec(mrb_state* mrb, const uint8_t data) {
// ...
// 刪除 int opext = 0;
}
因為有一些情況是我們希望「跳過」一定資訊的處理,因此增加了 SKIP
類型的巨集。
// lib/iron/opcode.h
// ...
#define SKIP_B() p++
#define SKIP_S() p+=2
#define SKIP_W() p+=3
#define SKIP_L() p+=4
並且更新相關的程式碼
// lib/iron/irep.h
// ...
#define IREP_PADDING() p += skip_padding(p)
#define IREP_SKIP_HEADER() SKIP_L(); SKIP_S(); SKIP_S(); SKIP_S()
#define IREP_SKIP_ISEQ() SKIP_L(); IREP_PADDING()
// lib/iron/irep.c
// ...
const uint8_t* irep_get(const uint8_t* p, int type, int n) {
SKIP_L();
SKIP_S(); // 刪除 uint16_t nlocals =
SKIP_S(); // 刪除 uint16_t nregs =
uint16_t nirep = READ_S();
// ...
// Find in POOL
{
uint32_t npool = READ_L();
if (type == IREP_TYPE_LITERAL) {
npool = n;
}
for(int i = 0; i < npool; i++) {
SKIP_B();
uint16_t len = READ_S();
p += len + 1; // End with null byte
}
if (type == IREP_TYPE_LITERAL) {
return p + 3; // Skip type and length
}
}
}
調整完畢後可以用 pio test
來確認沒有失誤,然後我們就可以用 USB 把開發版連接到電腦上,實際測試看看是否能正常輸出訊息。
使用 PlatformIO 的 upload
目標將原始碼上傳
pio run -e d1 -t upload
這邊因為 native
環境(桌機上的測試環境)無法被上傳,所以我們會再額外指定 -e
選項選擇要上傳的環境,如果板子的設定不一致的話也會無法上傳。
完成後我們可以用 pio device monitor
看到輸出(如果有支援)但目前會看到的是一大片的亂碼,這主要是我們連接上的時間點可能剛好不是有正確輸出的時間,另一方便是我們還沒有正確設定 Ardiuno 輸出的 Baud Rate 所以會出現亂碼,修改 src/main.app
加入以下程式碼
// src/main.app
// ...
void setup() {
Serial.begin(9600);
run_vm();
}
再次上傳後打開輸出的資訊,應該就能看到我們前面實作的 puts
方法所輸出的訊息,如果還是出現亂碼的話可以用 Ctrl+T
接著 Ctrl+D
兩次,讓板子重新啟動就可以了。
Ctrl+T
跟Ctrl+D
的操作每塊板子可能不太一樣,這塊 D1 Mini 很微妙的不是透過 Reset 重新啟動的。
下一篇我們會來設定 TFT 螢幕,讓我們可以在復古遊戲機所附的螢幕上面繪製一些文字或者圖形。