由于焊接工具沒還沒到位,于是最近幾篇文章主要以方案細(xì)化與軟件開發(fā)為主。今天這篇文章主要做一件比較有意義的事情:NDS端BASIC語言解釋器的移植與擴(kuò)展。文章附上了移植后軟件的運(yùn)行截圖和實(shí)機(jī)運(yùn)行效果,并在文章最后附上工程代碼,方便下載。順便打個(gè)廣告:有興趣的朋友可以加入QQ群:362445156 (Arduino極客群)。
一、為什么要移植和擴(kuò)展BASIC語言解釋器?
到目前為止,所有的NDS硬件擴(kuò)展,包括DS brut在內(nèi)都只提供SDK軟件開發(fā)庫。這使得你要進(jìn)行二次開發(fā),必須花大量時(shí)間去看SDK的文檔甚至代碼,工作量開銷較大。特別是以下兩個(gè)問題:(a). 對于想進(jìn)行快速測試一個(gè)外設(shè)的開發(fā)人員來說,閱讀文檔,源碼,以及在此基礎(chǔ)上進(jìn)行自行開發(fā)程序調(diào)試程序的時(shí)間會(huì)比較多。(b). 如果需要控制另一個(gè)外設(shè)或調(diào)整軟件功能時(shí),必須重新編譯程序,并將程序復(fù)制到NDS的燒錄卡內(nèi)。因此,工序多了很多步驟。
BASIC語言在誕生之初就以簡單易用為哲學(xué)指導(dǎo)。我將秉持該指導(dǎo)思想來做為本方案的Demo技術(shù)演示。如果能將BASIC語言的解釋器移植到NDS上,將有如下優(yōu)點(diǎn):
(1)利用NDS的觸屏可以作為字符輸入設(shè)備,效率很高。
(2)用BASIC語言編寫程序,非常容易,幾乎不用學(xué)習(xí),而且程序一般簡短易懂。
(3)通過BASIC語言方便快捷的寫程序,可以立桿見影,馬上看到執(zhí)行效果,無需在SDK上進(jìn)行再開發(fā),明顯提高開發(fā)效率。這解決了上述的問題(a).
(4)通過擴(kuò)展BASIC解釋器,為其加入對DLDI(在各NDS燒錄卡實(shí)現(xiàn)統(tǒng)一的文件讀寫功能)的支持,可以直接在NDS上將編好的程序?qū)懭霟浛ǖ腟D卡的文件內(nèi),也可以直接從SD卡內(nèi)將程序文件讀入內(nèi)存。這解決了上述的問題(b).
當(dāng)然沒有一個(gè)方案是十全十美的,相比直接在SDK上開發(fā),利用BASIC解釋器的缺點(diǎn)是,程序運(yùn)行速度沒有前者快。因此不適合作一些對外設(shè)SPI回傳數(shù)據(jù)響應(yīng)速度要求很高的場合。比如想把NDS做成一個(gè)邏輯分析儀。
二、移植BASIC語言解釋器
要移植BASIC解釋器,那么就得選擇一個(gè)目標(biāo)進(jìn)行移植。早在2007年,就已經(jīng)有一位網(wǎng)名叫zzo38computer的外國友人做了這個(gè)工作,項(xiàng)目名稱為DSBasic。他用來移植的BASIC解釋器源碼用C語言編寫,因此比較容易移植,只需要添加了NDS的軟鍵盤等功能。另外,網(wǎng)上流傳甚廣的開源BASIC解釋器源碼版本也比較多,比較有名的就是Tiny Basic。這個(gè)Tiny Basic說來話長,最早可以說到1975年。這里我們主要講一下我采用的代碼,來自TinyBASIC 2也采用的核心代碼BAS-INT.C這個(gè)文件。
經(jīng)過查看源碼發(fā)現(xiàn),原來DSBasic也是基于這個(gè)版本的代碼進(jìn)行擴(kuò)展的。而TinyBASIC 2的功能更加強(qiáng)大,還支持畫圖命令(需SDL庫支持,不過SDL庫開源且跨平臺(tái))。額外一提:自己用C/C++寫個(gè)BASIC解釋器不難,網(wǎng)上也有不少文章介紹,請google之。
下載了TinyBASIC 2源碼后,查看BAS-INT.C文件,該文件采用了較早的C語言語法。于是首先修改語法,然后用gcc在我的Mac OS X下順利編譯通過,試著運(yùn)行了幾個(gè)附帶的BASIC程序例子,一切順利。
接下來便是將代碼移植到NDS上。由于devkitPro并沒有提供太多的基于命令行的NDS開發(fā)示例程序。因此需要我加一些自己的代碼來實(shí)現(xiàn)簡單的光標(biāo)、scanf功能等。
整個(gè)移植過程就不詳述了,具體可以下載后面提供的源碼。這里主要講一下,移植的幾個(gè)要點(diǎn):
(1)添加光標(biāo)。我簡單的用"_",即下劃線代替光標(biāo),該光標(biāo)很簡單,不會(huì)閃爍,但基本達(dá)到使用的目標(biāo),除了一個(gè)小BUG:輸入文字到行末時(shí),會(huì)自動(dòng)跳到本行行首,而不是下一行。但該Bug不影響輸入的代碼。
(2)添加int get_input_number()函數(shù)實(shí)現(xiàn)INPUT命令的移植。因?yàn)槲沂褂糜|屏軟鍵盤后,NDS不支持scanf()函數(shù)從屏幕獲得輸入。
(3)添加"RUN"和"!"兩條命令來運(yùn)行程序。由于BAS-INT.C運(yùn)行程序是在命令行將需要執(zhí)行的BASIC程序作為命令行參數(shù)進(jìn)行調(diào)用執(zhí)行的,因此不支持程序編輯功能。而在NDS上我添加了一個(gè)非常簡單的程序輸入功能(包括上面提到的光標(biāo))。
圖1為移植成功后的運(yùn)行效果。下文將該移植到NDS的BASIC解釋器項(xiàng)目簡稱:NDSBasic。
圖1為最初植移的運(yùn)行界面,下方為觸屏,提供軟鍵盤進(jìn)行輸入。上屏為字符終端,和DOS,以及Terminal類似。
三、擴(kuò)展BASIC語言解釋器
該BASIC解釋器 (BAS-INT.C),提供的命令非常有限,因此需要自己擴(kuò)展添加新的BASIC語言命令。由于BAS-INT.C源碼本身編寫比較清晰,添加新命令過程非常簡單。只需以下幾步完成一個(gè)新命令添加:
(1)定義新命令宏,如 #define SEND 16
(2)在 struct commands 結(jié)構(gòu)體中添加命令的字符串,以及對應(yīng)的第(1)步中的宏,如"send", SEND,
(3)添加命令的執(zhí)行函數(shù)聲明,以及函數(shù)代碼,如void exec_send();
(4)在主函數(shù) (main)的switch命令中添加新命令的調(diào)用,如:
case SEND:
exec_send();
如果該命令除了一般的邏輯處理外需要用到NDS硬件等功能,則可在第(3)步代碼中調(diào)用外部函數(shù)完成相應(yīng)的硬件功能。這樣的設(shè)計(jì)代碼可移植性較好,邏輯功能代碼和硬件相關(guān)代碼分離。
我主要擴(kuò)展添加了以下幾條命令:
(1)"RUN" 或 "!":如上文所述。
(2)"LIST":打印內(nèi)存里的BASIC代碼到屏幕上。
(3)"NEW":清除內(nèi)存里的BASIC代碼,開始編寫新的程序代碼。
(4)"?":和PRINT命令一樣,用一個(gè)簡短的符號(hào),減少輸入時(shí)間。
(5)"SAVE filename":將當(dāng)前編輯的內(nèi)存里的代碼保存到filename文件中。
(6)"LOAD filename":將filename文件里的代碼讀入內(nèi)存。可以直接輸入命令"RUN"或"!"運(yùn)行。
(7)"PSET x,y,clr":畫像素。在 (x,y)處像素用clr號(hào)顏色點(diǎn)亮。
(8)"LINE x1,y1,x2,y2,clr [,B[F]]":從(x1,y1)到(x2,y2)用clr色號(hào)畫線、畫空心矩形、畫實(shí)心矩形命令。和QBASIC里的同名命令類似,區(qū)別是我為了方便命令的輸入,將QBASIC語法中的y1和x2之間的"-"改為了逗號(hào)","。另外,clr代表NDSBasic中的顏色號(hào)(預(yù)定義),"B"代表畫空心矩形,"BF"代表畫實(shí)心矩形。
(9)"CIRCLE x,y,r,clr":畫圓命令。x,y代表圓心,clr為顏色號(hào)。
(10)"DELAY ms":程序延遲ms毫秒。
(11)"SEND":發(fā)送數(shù)據(jù)到Arduino (Slot 1接口的SPI通道)上。我將該命令的語法設(shè)計(jì)成和PRINT一致,這樣使用起來比較靈活。
(12)"RECV n":從Arduino (Slot 1接口的SPI通道)上讀取n個(gè)字節(jié)的數(shù)據(jù),并打印讀取到的數(shù)據(jù)。默認(rèn)當(dāng)讀取過程中遇到'\0'字符時(shí)也會(huì)停止讀取。
(13)"CLS":清屏命令。執(zhí)行時(shí)將清除屏幕內(nèi)所有的打印信息。
所有命令不分大小寫,解釋器能自動(dòng)識(shí)別。實(shí)現(xiàn)過程中,
- LINE命令用到了我的開源3D引擎Nomad3D中的畫線代碼,支持Cohen裁剪功能,且執(zhí)行性能高效。
- CIRCLE命令則用到了我的另一篇博文:基于NDS/GBA/ARM,從啟動(dòng)到運(yùn)行你自己的第一行C程序代碼(NDS篇)中的畫圓算法,效率也很高。
- DELAY命令使用NDS的第0號(hào)硬件計(jì)時(shí)器 (Timer 0)實(shí)現(xiàn),精度達(dá)到微秒級。而且每次用完就釋放計(jì)時(shí)器,不占用硬件資源。
- SEND和RECV命令的實(shí)現(xiàn)用到了第三方庫:libspi-0.2 源碼,由于源碼對應(yīng)的devkitPro版本太早,源碼中用的很多宏已經(jīng)不存在或與當(dāng)前版本(我用的是最新的版本:devkitARM r42,libnds-1.5.8)沖突。因此我重新修改了源碼并編譯成libspi.a庫文件方便以后使用。
void exec_send()
{
//syntax: similar with PRINT
char send_str[256];
char recv_buff[256];
char temp[50];
memset(send_str,0,256);
memset(recv_buff,0,256);
memset(temp,0,50);
sprintf(temp," ");
strcat(send_str,temp);
}
void do_send(char* send_str, char* recv_buff, int max_len)
{
int i=0;
while(send_str[i] && i<= max_len)
{
recv_buff[i] = send_str[i];
i++;
}
recv_buff[i]='\0';
////////
int len=strlen(send_str);
char* p=send_str;
setupConsecutive_cardSPI(len);
while(*p)
writeBlocking_cardSPI(*(p++));
}
RECV命令實(shí)現(xiàn)源碼:
void exec_recv()
{
int num_recv_byte=0;
int num_byte=0;
get_exp(&num_byte);
memset(recv_buff,0,MAX_RECV_SIZE);
num_recv_byte = do_recv(recv_buff, num_byte, STOP_CHAR);
//the variable Z used to store the number of received byte.
//variables['Z'-'A'] = num_recv_byte;
printf("[received %d byte: %s]\n", num_recv_byte, recv_buff);
}
int do_recv(char* buff, int num_byte, char stop_byte)
{
u8 read_byte=0;
int i=0;
for(i=0; i
{
}
最后,因?yàn)樘砑恿水媹D功能,我將終端窗口從上屏移到下屏,和軟鍵盤放在一個(gè)屏內(nèi)。終端窗口提供14行代碼顯示,上屏全部用來畫圖。最后運(yùn)行效果如圖2, 圖3, 圖4所示。
圖2. LINE命令執(zhí)行效果。

圖3. LINE、CIRCLE和DELAY命令在循環(huán)中執(zhí)行的效果。

圖4. 實(shí)機(jī)運(yùn)行效果。
基于Slot 1接口SPI通信的SEND, RECV命令沒能實(shí)測,但模擬器上執(zhí)行來看應(yīng)該是工作正確的。
代碼下載:NDSBasic + libspi
后續(xù)將先測試SEND和RECV命令,然后添加以下命令:
- DWRITE pin,value:設(shè)置Arduino的第pin數(shù)字引腳為值value (1: HIGH高電平,0:LOW低電平)。
- AWRITE pin,value:設(shè)置Arduino的第pin (PWM引腳)為值value (0~255之間的值,用于PWM信號(hào))。
- DREAD( pin ):讀取第pin數(shù)字引腳的電平狀態(tài) (1:高電平,0:低電平)。
- AREAD( analogPin ):讀取第analogPin模擬引腳的值 (0~1023之間)。
后記:
當(dāng)時(shí)在考慮加畫圖命令時(shí)有兩種方案,除了當(dāng)前使用的方案外,另一種可選方案是:
(1)仍然使用上屏作為主終端屏幕,當(dāng)執(zhí)行到畫圖命令時(shí)上屏自動(dòng)切換到畫圖狀態(tài)(由于NDS硬件原因,同一屏幕畫圖狀態(tài)和顯終端狀態(tài)不可同時(shí)存在)。下屏軟鍵盤上方14行只用于顯示與Arduino的SPI通信的數(shù)據(jù)(發(fā)送和接收數(shù)據(jù)),不作他用。
本篇結(jié)束,后面將會(huì)涉及Arduino端具體的SPI通信代碼設(shè)計(jì)與編寫。