改寫我自己 25 年前寫的 BASIC 程式,把它移植到 Arduino Uno 開發板 — — 簡單玩 Tiny BASIC Plus 直譯器,回顧老科技時代

Photo by Annie Spratt on Unsplash

我第一次(自)學程式是小六畢業的暑假。小學時已經有電腦課,只上一些簡單的作業系統跟文書、繪圖操作。當時不知在哪看到說 MS-DOS 5.0 有附一個可以寫程式的東西叫 QBasic.exe,結果就去書店找了一本 Quick Basic 的中文電腦書來學,並在那個暑假寫了我的頭幾支程式。

(QB 是所謂的 Structured BASIC,它不再需要在每一行前面寫行號,功能也更強大。此外 QBasic 其實又是 Quick Basic 的簡化版,拿掉了編譯可執行檔的功能。)

在那個台灣還沒有 STEM 教育、資訊素養若有可無的年代,寫程式的概念跟最佳實踐方式沒人教,就只能自己胡亂摸索。但也因為這樣,國中開始自學 Visual Basic 4(高中時是用 6.0),到大學時雖然沒有考進資訊科技科系,但仍嘗試學了一陣子 VB.NET 和 ASP.NET… 所以我其實有過不短的歲月都在寫 BASIC 語言,一直到出社會為止。

前陣子整理東西時,翻出那本電腦書的 5.25 吋範例磁片(容量還只有 360 KB 而不是 1.2 MB),以及存有我當年那些舊程式的 3.5 吋磁片。我突然有個衝動,想仔細看看當年那些程式碼。多年前我曾用家中的舊筆電讀過一次,但當時沒有保留下來。

這不成問題,因為網路上找得到 USB 外接 3.5 吋軟碟機。此外,我也想執行這些程式,可以的話把它改進一下,當成某種對自己的回顧。

結果,就在尋找可執行的 QBasic 編輯器的過程中,我發現了一個東東:Tiny BASIC。

Photo by bert brrr on Unsplash

BASIC(Beginner’s All-purpose Symbolic Instruction Code,初學者的全功能符號指令語言)為 John G. Kemeny 和 Thomas E. Kurtz 帶領達特茅斯學院的研究生在 1964 年推出的一種入門程式語言。這種語言以 FORTRAN II 為基礎,但設計得對初學者十分「友善」(至少跟 FORTRAN 和 ALGOL 相比是如此):

雖然起先 BASIC 的目的是讓校內學生都能學會寫程式、並用來輔佐數學課,但它在校外也大受歡迎,於是設計者們公開了 BASIC 的編譯器。

那時電腦市場的主力是大型主機,讓使用者用分時系統(time-sharing)的方式從終端機連線來租用電腦時間,BASIC 無須編譯的快速執行速度,使得多重使用者都能在終端機前快速取得結果。話說回來,小型化的「迷你電腦」、「DIY 電腦」 也在 70 年代中開始出現,BASIC 直譯器對記憶體的低需求剛好很適合它們。在這類電腦上,BASIC 語言甚至扮演了作業系統和驅動程式的角色。

在這當中,真正推波助瀾的來自 Altair BASIC — — 由 Paul Allen 和 Bill Gates 在 1975 年替公認第一台個人電腦 Altair 8800 所開發的 BASIC 編譯器。這件事的成功,使得兩人創立了微軟,而該公司最早期的產品都是從此延伸而來,一直到微軟轉而主打作業系統為止。

當然,不是所有人都喜歡 BASIC。荷蘭電腦科學家 Edsger Dijkstra 就曾出名地說:「實務上,要對已接觸過 BASIC 的學生教導優秀程式設計是不可能的,他們的心智已經被腐化到回天乏術。」當然他也批評當時最紅的其他語言,說 APL「是個錯誤」,而教 COBOL 的行為「應視為犯罪」。

在那段日子,BASIC 就像今日的 Python 一樣紅遍半邊天。BASIC 成了眾家產品的大賣點,許多電腦甚至擁有裝有 BASIC 直譯器的唯讀記憶體。

讀取磁片

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 也有繪圖功能,只是就從沒研究過了。

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

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 的形式加了兩條斜線,讓它們顯眼一點。縮排同樣並非必要,不過看起來總是美觀一點。

最後面可見我加了一個 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 記憶體。他還在原始碼中留下下面這個有趣的「版權宣告」:

下面寫的是「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:

其實想想,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,應該會看到如下結果:

這就是我們的 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 也是這樣運作的。你可以一次執行一行簡單的程式:

或者輸入一個完整的程式(下面是個計算費式數列的簡單實作),然後再執行它:

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

可用的指令與範例請參考其 Github 頁面:

https://github.com/BleuLlama/TinyBasicPlus

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

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

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

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

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

Tiny BASIC Plus 的設定

Photo by Clay Banks on Unsplash

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

  • #define ENABLE_FILEIO 1:啟用 SD 卡檔案讀寫(很占記憶體)
  • #define ENABLE_AUTORUN 1:啟用自動從 SD 卡執行程式(前提是有啟用 ENABLE_FILEIO)
  • #define kAutorunFilename “autorun.bas”:要從 SD 卡讀取的檔名
  • #define ENABLE_EAUTORUN 1:啟用自動從 EEPROM 執行程式
  • #define ENABLE_TONES 1:啟用外接蜂鳴器功能
  • #define kPiezoPin 5:外接蜂鳴器 GPIO 腳位(預設 5)
  • #define ENABLE_EEPROM 1:啟用 EEPROM
  • #define kConsoleBaud 9600:UART baud rate 設為 9600

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

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

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

From QB to TB

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

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

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

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

哇,快滿了。接著重開 Arduino 來玩遊戲吧:

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

結語

Photo by Amr Taha™ on Unsplash

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

或者,用 BBC micro:bit 如何?

I just like to write weird stuff that have very little to do with my actual work. My normal blog is https://krantasblog.blogspot.com.

I just like to write weird stuff that have very little to do with my actual work. My normal blog is https://krantasblog.blogspot.com.