改寫我自己 25 年前寫的 BASIC 程式，把它移植到 Arduino Uno 開發板 — — 簡單玩 Tiny BASIC Plus 直譯器，回顧老科技時代
By: Alan Wang
Tags: Basic Language, Quickbasic, Arduino, STEM, Retro

讀取磁片

Photo by Brian Kairuz on Unsplash

軟碟機送來了，似乎是中國廠商拿 TEAC 的軟碟機改裝成 USB 隨插版，號稱免驅動程式。然而，儘管我的 Windows 10 能偵測到它，卻似乎無法正常的讀取磁片。

接著我靈機一動，把它接到我的樹莓派 3B+ 上。Linux 系統對舊設備的支援一向很好；以前我就用過改裝了 Ubuntu 的舊筆電來接超老舊的掃瞄機。

磁片一塞進去，結果馬上就出來了。

這磁片裡有幾支程式其實是當時 QBasic 附的範例（例如著名的 Gorilla.bas，你可以在這裡線上玩），被我留下當備份，但現在 Github 上也找得到。剩下我自己寫的程式，有些只是做簡單的算術，有些是透過 BASIC 來跑 MS-DOS 指令，也有簡易的計時跟檔案管理程式。

在這裡面，我寫的最後一支程式是 ROCK.bas，內容是撿石頭遊戲，在國外叫做 Nim。我這支程式的規則是有 21 顆石頭，兩個人輪流拿 1~4 顆，拿到最後一顆的人贏。這其實是個著名的策略遊戲，很常用在數學分析，不過在這裡就單純是個小遊戲，而且還是我當時自己想辦法實作的遊戲。儘管內容很簡單，但就是很有紀念意義。

下面我們就用這支程式來延伸吧。

在 QBasic 執行 .bas 檔

Photo by Tracy Adams on Unsplash

許多人仍然記得 BASIC 的歲月，所以網路上很容易能找到各種能在新系統執行的 BASIC 編譯器，例如 QB64。不過下面我還是要用原始的 QBasic.exe，透過 Dosbox 執行。

幸運的是，有人做了個 Basic 2 DosBox 來簡化安裝：

下載 .zip 後執行裡面的 basic2dos.exe，勾選 QBASIC 1.1，並選一個資料夾為 BASIC 目的地。安裝程式會在那裡放你勾選的 BASIC 編譯器和一份 Dosbox，而子目錄 _shortcut 會有程式捷徑，能一氣呵成執行 Dosbox 接著啟動 BASIC。（你可以把 .bas 檔拷貝到 _floppy 子目錄，那裡會模擬成磁碟 A。）

我根據 Dosbox 指南把 CPU 速度調到接近 486 處理器的程度（約 28K cycle），雖然這可能對程式執行上差異不大。接著就從編輯器內打開檔案：

載入程式後就可以執行了：

如果是 Quick Basic 4.5，Run 選單下面就會有編譯 .exe 的選項。至於 QB64 則是會先強制編譯成執行檔。

執行畫面如下：

BASIC 蠻好玩的，可以像 PTT 的控制碼一樣控制字的顏色。其實 QB 也有繪圖功能，只是就從沒研究過了。

現在我們來看看這個程式的內容。此外為了回顧這個語言（當年自己也沒完全摸熟）的語法，我參考了以下資源：

CLS

COLOR 9, 7

PRINT "GAMES-NO_1 PICKING ROCKS"

PRINT

COLOR 7, 0

PRINT "START GAME, ENTER <S>. HELP, ENTER <H>. QUIT, ENTER <Q>."

OLD:

INPUT "YOUR CHOISE :", A$

SELECT CASE A$

CASE IS = "S", "s"

GOTO AGAIN

CASE IS = "H", "h"

GOTO HELP

CASE IS = "Q", "q"

GOTO QUIT

CASE ELSE

CLS

GOTO OLD

END SELECT

CLS

AGAIN:

CLS

PRINT

LINE INPUT "PLAYER_1 'S NAME : ", B$

IF B$ = "" THEN LET B$ = "PLAYER_1"

PRINT

LINE INPUT "PLAYER_2 'S NAME : ", C$

IF C$ = "" THEN LET C$ = "PLAYER_2"

CLS

PRINT

N% = 21

DO

CLS

COLOR 14, 0

PRINT "ROCKS ="; N%

COLOR 7, 0

PRINT

PRINT B$; "'S TURN"

GAME1:

INPUT "HOW MANY ROCKS DO YOU PICK (1-4) ? ", D%

IF D% <= 4 THEN GOTO GAME2

COLOR 15, 0

PRINT "ERROR !!! ONLY 1-4 !!!"

COLOR 7, 0

BEEP

GOTO GAME1

GAME2:

LET N% = N% - D%

PRINT

COLOR 14, 0

PRINT "ROCKS ="; N%

COLOR 7, 0

IF N% <= 0 THEN GOTO WIN1

PRINT

PRINT C$; "'S TURN"

GAME3:

INPUT "HOW MANY ROCKS DO YOU PICK (1-4) ? ", E%

IF E% <= 4 THEN GOTO GAME4

COLOR 15, 0

PRINT "ERROR !!! ONLY 1-4 !!!"

COLOR 7, 0

BEEP

GOTO GAME3

GAME4:

LET N% = N% - E%

IF N% <= 0 THEN GOTO WIN2

LOOP

WIN1:

CLS

COLOR 26, 0

PRINT C$; " ,YOU WIN !!!"

BEEP

COLOR 7, 0

GOTO OVER

WIN2:

CLS

COLOR 26, 0

PRINT B$; " ,YOU WIN !!!"

BEEP

COLOR 7, 0

OVER:

PRINT

PRINT

INPUT "QUIT ? (Y/N)", F$



IF F$ = "Y" OR F$ = "y" THEN GOTO QUIT

CLS

GOTO OLD

QUIT:

PRINT

COLOR 1, 7

PRINT "GOOD BYE !!!!! THANK YOUR PLAYING !!!!!"

COLOR 7, 0

END

HELP:

CLS

COLOR 11, 0

PRINT "THERE'S 21 ROCKS."

PRINT "YOU HAVE TO PICK 1-4 ROCK(S) IN EACH TIME."

PRINT "DON'T PICK MORE."

PRINT "WHO PICK THE LAST ROCK, WHO LOST."

PRINT

PRINT "(AND, IF YOU DON'T PRESS YOUR NAME,"

PRINT "IT WILL BE <PLAYER_A> OR <PLAYER_B>.)"

PRINT

PRINT

PRINT

PRINT

COLOR 1, 7

PRINT "MADE BY ALAN WANG . IN 1996.8"

COLOR 7, 0

PRINT

PRINT

PRINT "PLEASE PRESS ANY KEY TO PLAY :"

G$ = INPUT$(1)

CLS

GOTO OLD

BASIC 最特別（或惡名昭彰）的地方之一就是 GOTO 指令，可以跳到程式中的某一行。現在會用的主流語言都不喜歡這種有結構破壞性的玩意（當然有人說 switch…case 就是某種 goto），不過在那個時候就很容易倚賴它作為控制程式流程的主要手段。既然 BASIC 沒有迴圈的 break 或 continue，所以在某些時候用它會比寫迴圈停止條件來得簡單。

其實 C++ 和 Go 語言之類還是有 goto，而且也還是有人會用…

本篇我就不多講 BASIC 的語法，但看得出來我那時是用非常線性的思維來寫的，直接用 GOTO 標籤區分程式的幾個部位 ，壓根不知道有 function、subroutine 可用，不懂迴圈有條件式，變數名稱也是用英文 ABC 下去取名（這說不定是從書上學來的壞習慣）。此外，由於兩位玩家有各自的名稱變數，變成一樣的遊戲邏輯就得寫兩次。

Photo by Markus Spiske on Unsplash

我在修改版放棄了給使用者取名的設計（我想我當時受到 Gorilla.bas 不小的影響），直接用 0 和 1 代表（輸出時是 Player 1 和 2），這樣就能共用同一套邏輯。我也用了比較新的 DIM 變數宣告語法 — — 原本是用 % 和 $ 來隱含表示變數為整數跟字串型別 。

下面我也用單引號 ‘ 寫點註解，但我還是仿 C 的形式加了兩條斜線，讓它們顯眼一點。縮排同樣並非必要，不過看起來總是美觀一點。

'// BASIC Stone Picking Game ver.2

'// by Alan Wang, 2021 '// declare variables and a subroutine

DECLARE SUB help ()

DIM turns AS INTEGER

DIM stones AS INTEGER

DIM picks AS INTEGER

DIM choice AS STRING start: '// display menu

CLS

COLOR 6, 0

PRINT "[BASIC stone picking game]"

COLOR 7, 0

PRINT "-------------------------"

PRINT "<S> = Start to play"

PRINT "<H> = Help (game rule)"

PRINT "<Q> = Quit"

PRINT '// read user's choice

WHILE 1

'// read players' choice

COLOR 15, 0

INPUT "Choice: ", choice

COLOR 7, 0

'// get the first letter in upper case

choice = UCASE$(LEFT$(choice, 1))

SELECT CASE choice

CASE IS = "S":

GOTO game '// start the game

CASE IS = "H":

CALL help '// call the subroutine

CASE IS = "Q":

END '// end the program

CASE ELSE

COLOR 4, 0

PRINT "Invalid choice."

END SELECT

WEND game: '// start the game

CLS

turns = 0

stones = 21 DO

CLS

COLOR 2, 0

PRINT "[Player"; (turns + 1); "'s turn]"

COLOR 15, 0

PRINT ">"; stones; "stone(s) left."

tryinput:

COLOR 7, 0

INPUT "How many do you wish to pick? (1-4): ", picks

IF picks >= 0 AND picks <= 4 THEN

'// pick stone(s) from the pile and switch player

stones = stones - picks

turns = 1 - turns

ELSE

COLOR 4, 0

PRINT "Incorrect number. Try again."

GOTO tryinput

END IF

LOOP UNTIL stones <= 0 '// no more stones, exit loop '// the game ends

CLS

COLOR 30, 0

PRINT "Player"; ((1 - turns) + 1); "wins!"

PRINT '// go back to the top if the user wish to play again

COLOR 7, 0

INPUT "Would you like to try again? (y/N): ", choice

IF UCASE$(LEFT$(choice, 1)) = "Y" THEN GOTO start '// subroutine: display help message

SUB help

PRINT

COLOR 9, 0

PRINT "##### Game rule ##############################"

COLOR 8, 0

PRINT "There are total 21 stones. Two players will be"

PRINT "taking turns to pick 1-4 stones."

PRINT "The player who get the last stone wins."

PRINT

COLOR 7, 0

END SUB

最後面可見我加了一個 subroutine 來顯示遊戲規則，有趣的是 QBasic 會把它顯示在獨立的視窗內（要用選單的 View 去切換），免得你誤刪它們。subroutine 很像 function，差別在於 function 可以當成運算式的一部分呼叫，前者則不行。

Tiny BASIC 登場

Photo by Hannah Gullixson on Unsplash

現在我們又要回來講 BASIC 的歷史了。

前面提過，Bill Gates 做出的第一個產品是 Altair BASIC 對吧？它的售價取決於版本，要 150 至 300 美元不等。但 Altair BASIC 太受歡迎，當時的矽谷駭客俱樂部 Homebrew Computer Club 馬上就有人大量盜拷和散布它，那時的儲存媒介還是打孔紙帶。這使得 Gates 隔年在各大雜誌發表了措辭嚴厲的公開信，指責這些「盜取自己軟體」的行為。

Steve Wozniak 也是 Homebrew Computer Club 成員。正因在第一次參加集會時看到 Altair BASIC，他決定自己替 Apple I 和 Apple II 寫一個叫 Integer BASIC 的版本，雖然後來蘋果還是買了微軟的版本來改造。

Altair BASIC 的程式紙帶

當然，有許多人不予認同，堅信軟體應該是免費的（更別提有人指出 Gates 自己就曾偷用哈佛的 PDP-10 電腦時間來開發程式）。這也促使人們投入一個當時已經發展中的軟體 Tiny BASIC— — 一個更加簡化、能在各種 8 位元微處理器電腦上運作的 BASIC 直譯器。這些 Tiny BASIC 原始碼更在雜誌上完整公開，讓任何人都能輸入自己的電腦。

在這當中，最著名的版本出自另一位 Homebrew Computer Club 成員、來自台灣的王理瑱（Li-Chen Wang）所設計的 Palo Alto Tiny BASIC，只需 1.77 KB 記憶體。他還在原始碼中留下下面這個有趣的「版權宣告」：

;*********************************

;

; TINY BASIC FOR INTEL 8080

; VERSION 2.0

; BY LI-CHEN WANG

; MODIFIED AND TRANSLATED

; TO INTEL MNEMONICS

; BY ROGER RAUSKOLB

; 10 OCTOBER,1976

; @COPYLEFT

; ALL WRONGS RESERVED

;

;*********************************

下面寫的是「copyleft」而且「all wrongs reserved」（笑）（這裡面的另一個名字 Roger Rauskolb，當時幫忙改良了 Palo Alto Tiny BASIC。）後來 copyleft 的概念也被寫入了 1985 年的 GNU 宣言。

Tiny BASIC 自然不是較晚期 Quick Basic 那種結構化 BASIC，它一樣得有行號，而且功能也更加簡化。幾個值得注意的特色像是：

資料型別只有 16 進位整數（範圍：正負 32767）

變數只有 A~Z 這 26 個名稱可用

取決於版本，行數有限（如 255、32767 或 65535 行）

沒有 AND、OR 這類邏輯運算子

主要的錯誤訊息就只是「What?」（語法錯誤）、「How?」（語法正確但值有誤等等）、「Sorry!」（程式正確但記憶體不足）

這樣子還有辦法寫程式嗎？請待下面分曉。

Tiny BASIC Plus on Arduino

Photo by Niclas Illg on Unsplash

基於我現在對微控制器和創客領域的超業餘興趣，我自然也試著搜尋，看有沒有人把 BASIC 移植到開發板上。結果還真的有。

下面我們要使用的是 Scott Lawrence 替 Arduino AVR 系列處理器開發板而寫的 Tiny BASIC Plus，它源自 Michael Field 的 C 語言移植版，而後者又源自 Gordon Brandly 在 1984 年替 Motorola 68000 處理器寫的版本，據信根據的就是 Palo Alto Tiny BASIC：

******************************************************************

* *

* Tiny BASIC for the Motorola MC68000 *

* *

* Derived from Palo Alto Tiny BASIC as published in the May 1976 *

* issue of Dr. Dobb's Journal. Adapted to the 68000 by: *

* Gordon Brandly *

* 12147 - 51 Street *

* Edmonton AB T5W 3G8 *

* Canada *

* (updated mailing address for 1996) *

* *

* This version is for MEX68KECB Educational Computer Board I/O. *

* *

******************************************************************

* Copyright (C) 1984 by Gordon Brandly. This program may be *

* freely distributed for personal use only. All commercial *

* rights are reserved. *

******************************************************************

其實想想，Uno／Nano 那 8 位元、時脈 16 MHz、只有 2 KB 記憶體的 ATmega328P 處理器，跟最早期的 DIY 電腦確實也相去不遠。所以感覺就像整整繞了一圈，只是硬體已經縮小許多而已。

關於這個直譯器的兩三事：

只要透過 Arduino IDE 編譯和上載一個 .ino 草稿檔即可 — — 這支 Arduino C++ 程式會變成 Tiny BASIC 直譯器，可透過 UART 互動。

支援 GPIO 腳位輸出入和蜂鳴器控制。

支援於外部 SD 卡和 AVR 開發板的 EEPROM 記憶體讀寫程式。

其實也真的有人嘗試把 Arduino 接上螢幕與鍵盤，變成名副其實的 8 位元「電腦」。

上傳 Tiny BASIC Plus

Photo by STIL on Unsplash

首先下載或複製下面的 .ino 草稿碼：

接著在 Arduino IDE 打開或貼上：

我目前用的是 Arduino IDE 2.0 beta 5，以 VS Code 為基礎。可以用暗色系畫面、能多顯示一點錯誤訊息真是太好了，但除此之外感覺還沒有很大差異。

目前你還不需要做任何修改。但程式會引用 SD 這個函式庫，正常下 Arduino IDE 應該會有內建，沒有的話就記得裝一下，否則會編譯失敗。本篇就不多講 Arduino IDE 教學了。記得選擇正確的板子型號和 port。

上傳完畢後，打開序列埠監控視窗，baud rate 設 9600，換行選 New Line，應該會看到如下結果：

HELLO

TinyBasic Plus v0.15

999 bytes free.

1024 EEProm bytes total.

OK

>

這就是我們的 Tiny BASIC 直譯器，它指出板子上有近 1 KB 記憶體可用，而 EEPROM 的 1KB 儲存空間也是空的。

ATmega328P（Uno／Nano）和 ATmega32u4（Leonardo／Micro） 微處理器都有 1KB EEPROM，而 Arduino Mega 2560 的 ATmega2560 有 4 KB — — 以上這些板子都確認可上傳和使用 Tiny BASIC Plus。

輸入和執行 Tiny BASIC 程式

Photo by Markus Spiske on Unsplash

現在我們要回到過去，體驗在終端機上寫 BASIC 程式是什麼感覺了。

以前沒有 QBasic 這類編輯器，BASIC 直譯器就是你的編輯器。你必須逐行輸入帶有行號的程式（它們會存在記憶體中），最後整個執行。如果程式有誤，就要重新輸入某一行。

Tiny BASIC Plus 也是這樣運作的。你可以一次執行一行簡單的程式：

>PRINT "Hello World!"

Hello World!

>A = 1

>PRINT A

1

>PRINT A + 2

3

>A = A + 9

>PRINT A

10

或者輸入一個完整的程式（下面是個計算費式數列的簡單實作），然後再執行它：

>10 A = 1

>15 PRINT A

>20 B = 1

>30 FOR I = 0 to 10

>40 PRINT B

>50 C = B

>60 B = A + B

>70 A = C

>80 NEXT I

>LIST

10 A = 1

15 PRINT A

20 B = 1

30 FOR I = 0 TO 10

40 PRINT B

50 C = B

60 B = A + B

70 A = C

80 NEXT I

OK

>RUN

1

1

2

3

5

8

13

21

34

55

89

144

OK

當你輸入有行號的程式碼時，它不會被執行，而是被放進記憶體和按照行號排列。LIST 會列出目前 RAM 內儲存的程式，RUN 則能執行它。MEM 會傳回目前的記憶體用量（看來我們用掉了 100 bytes）：

>MEM

899 RAM bytes free.

1024 EEProm bytes total.

1024 EEProm bytes available.

可用的指令與範例請參考其 Github 頁面： https://github.com/BleuLlama/TinyBasicPlus

如果你使用某些終端機軟體，也可以直接複製貼上多行內容，但每次都要重打程式很麻煩吧？因此設計者提供了兩種儲存程式的辦法，一個是透過外接 SD 卡模組，二是使用 Arduino 自身的 EEPROM（快閃記憶體的前身）。

…然而不知為何，我一直沒辦法讓程式從 SD 模組讀取檔案（儘管它在 SD 函式庫的範例程式下就很正常）。所以唯一的解法就是使用 EEPROM 了。

>EFORMAT

................

>ESAVE

OK

>ELIST

10 A = 1

15 PRINT A

20 B = 1

30 FOR I = 0 TO 10

40 PRINT B

50 C = B

60 B = A + B

70 A = C

80 NEXT I

EFORMAT 會格式化（清空）EEPROM，你在使用前請先這麼做一次。ESAVE 會將 RAM 記憶體的程式存入 EEPROM，而 ELIST 則可列出它（就像 LIST 是列出 RAM 的內容）。

現在按一下 Arduino 的 reset 鈕，看看會發生什麼事：

>TinyBasic UNO v0.15 1

1

2

3

5

8

13

21

34

55

89

144

OK

Arduino Uno 重開後，直譯器自動執行了 EEPROM 內的程式！這表示我們成功將 BASIC 程式保存在板子上，可以重複使用了。

Tiny BASIC Plus 的設定

Photo by Clay Banks on Unsplash

現在來看 .ino 草稿碼，裡面其實有一系列可以開關的設定：

#define ENABLE_FILEIO 1 ：啟用 SD 卡檔案讀寫（很占記憶體）

：啟用 SD 卡檔案讀寫（很占記憶體） #define ENABLE_AUTORUN 1 ：啟用自動從 SD 卡執行程式（前提是有啟用 ENABLE_FILEIO）

：啟用自動從 SD 卡執行程式（前提是有啟用 ENABLE_FILEIO） #define kAutorunFilename “autorun.bas” ：要從 SD 卡讀取的檔名

：要從 SD 卡讀取的檔名 #define ENABLE_EAUTORUN 1 ：啟用自動從 EEPROM 執行程式

：啟用自動從 EEPROM 執行程式 #define ENABLE_TONES 1： 啟用外接蜂鳴器功能

啟用外接蜂鳴器功能 #define kPiezoPin 5 ：外接蜂鳴器 GPIO 腳位（預設 5）

：外接蜂鳴器 GPIO 腳位（預設 5） #define ENABLE_EEPROM 1 ：啟用 EEPROM

：啟用 EEPROM #define kConsoleBaud 9600：UART baud rate 設為 9600

既然 SD 卡讀寫不順利，最前面三個可以關閉（檔案讀寫預設沒有啟用）。蜂鳴器預設是不啟用的，但你手上有無源蜂鳴器的話可以打開（後面會再看到）。

這檔案其實還支援 ESP8266，但你得把 EEPROM 功能關掉才能順利編譯。這自然意味著你在 ESP8266 上沒有辦法於內部記憶體保存程式。

要注意有的 #define 後面有 #undef，功能要啟用的話記得把 #undef 註解掉，不然就還是不會開啟了。

From QB to TB

現在我要來把撿石頭遊戲移植到 Tiny BASIC Plus 上，而且得克服後者的語法限制。

其實做起來沒有想像難，這也意味著你其實只要一些非常簡單的功能就能實作出有一定複雜度的程式。只是在電腦設備的突飛猛進下，這麼做早就落伍了。

這個版本我是先用筆記本寫的，以便快速修改行號。而為了方便整批貼上，我也改用 Tera Term 終端機軟體來連線。

10 PRINT""

20 PRINT "[Tiny BASIC stone picking game]"

30 PRINT "-------------------------------"

40 PRINT "<1> = Start to play"

50 PRINT "<2> = Help (game rule)"

60 PRINT "<3> = Quit"

70 PRINT "Choice:"

80 INPUT C

90 IF C = 1 GOTO 130

100 IF C = 2 GOSUB 310

110 IF C = 3 END

120 GOTO 10

130 T = 0

140 S = 21

150 PRINT ""

160 PRINT "[Player ", T + 1, "'s turn]"

170 PRINT S, " stone(s) left."

180 PRINT "How many do you wish to pick? (1-4)"

190 INPUT C

200 IF C < 1 GOTO 180

210 IF C > 4 GOTO 180

220 S = S - C

230 T = 1 - T

240 IF S > 0 GOTO 150

250 PRINT ""

260 PRINT "=== PLAYER ", 2 - T, " WINS! ==="

270 PRINT "Would you like to try again? (<1>=y/<0>=N)"

280 INPUT C

290 IF C = 1 GOTO 10

300 END

310 PRINT ""

320 PRINT "##### Game rule ##############################"

330 PRINT "There are total 21 stones. Two players will be"

340 PRINT "taking turns to pick 1-4 stones."

350 PRINT "The player who get the last stone wins."

360 RETURN

輸入於 Arduino Uno 執行的 Tiny BASIC Plus 直譯器後（當然是我已經測試過 OK 的），用 ESAVE 把程式存入 EEPROM，然後來看看用了多少記憶體：

>ESAVE

OK

>MEM

87 bytes free.

1024 EEProm bytes total.

49 EEProm bytes available.

哇，快滿了。接著重開 Arduino 來玩遊戲吧：

所以，就這樣 — — 我把自己 25 年前的作品改寫成了能在 Arduino 執行的版本。

結語

Photo by Amr Taha™ on Unsplash

其實 Tiny BASIC Plus 可講的還不只如此：它實作了基本的 GPIO 腳位跟蜂鳴器控制（我的 BASIC 舊程式裡就很常用電腦蜂鳴器放音樂）。雖然能做的不多，但確實有發揮空間，而且你也可以把程式永久存在 EEPROM 內。只是這篇文已經拖得夠長了，所以先告一段落吧。也許下篇會來試試看用 Tiny BASIC + Arduino Mega 2560 做創客？

或者，用 BBC micro:bit 如何？