<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> Arduino >> 瀏覽文章

        擴展NDS掌機連接Arduino (5)-NDS端BASIC語言解釋器的移植與擴展

        作者:c_gao   來源:c_gao   點擊數:  更新時間:2014年07月27日   【字體:

        由于焊接工具沒還沒到位,于是最近幾篇文章主要以方案細化與軟件開發為主。今天這篇文章主要做一件比較有意義的事情:NDS端BASIC語言解釋器的移植與擴展。文章附上了移植后軟件的運行截圖和實機運行效果,并在文章最后附上工程代碼,方便下載。順便打個廣告:有興趣的朋友可以加入QQ群:362445156 (Arduino極客群)。

         
        一、為什么要移植和擴展BASIC語言解釋器?
         
        到目前為止,所有的NDS硬件擴展,包括DS brut在內都只提供SDK軟件開發庫。這使得你要進行二次開發,必須花大量時間去看SDK的文檔甚至代碼,工作量開銷較大。特別是以下兩個問題:(a). 對于想進行快速測試一個外設的開發人員來說,閱讀文檔,源碼,以及在此基礎上進行自行開發程序調試程序的時間會比較多。(b). 如果需要控制另一個外設或調整軟件功能時,必須重新編譯程序,并將程序復制到NDS的燒錄卡內。因此,工序多了很多步驟。
         
        BASIC語言在誕生之初就以簡單易用為哲學指導。我將秉持該指導思想來做為本方案的Demo技術演示。如果能將BASIC語言的解釋器移植到NDS上,將有如下優點:
        (1)利用NDS的觸屏可以作為字符輸入設備,效率很高。
        (2)用BASIC語言編寫程序,非常容易,幾乎不用學習,而且程序一般簡短易懂。
        (3)通過BASIC語言方便快捷的寫程序,可以立桿見影,馬上看到執行效果,無需在SDK上進行再開發,明顯提高開發效率。這解決了上述的問題(a).
        (4)通過擴展BASIC解釋器,為其加入對DLDI(在各NDS燒錄卡實現統一的文件讀寫功能)的支持,可以直接在NDS上將編好的程序寫入燒錄卡的SD卡的文件內,也可以直接從SD卡內將程序文件讀入內存。這解決了上述的問題(b).
         
        當然沒有一個方案是十全十美的,相比直接在SDK上開發,利用BASIC解釋器的缺點是,程序運行速度沒有前者快。因此不適合作一些對外設SPI回傳數據響應速度要求很高的場合。比如想把NDS做成一個邏輯分析儀。
         
        二、移植BASIC語言解釋器
         
        要移植BASIC解釋器,那么就得選擇一個目標進行移植。早在2007年,就已經有一位網名叫zzo38computer的外國友人做了這個工作,項目名稱為DSBasic。他用來移植的BASIC解釋器源碼用C語言編寫,因此比較容易移植,只需要添加了NDS的軟鍵盤等功能。另外,網上流傳甚廣的開源BASIC解釋器源碼版本也比較多,比較有名的就是Tiny Basic。這個Tiny Basic說來話長,最早可以說到1975年。這里我們主要講一下我采用的代碼,來自TinyBASIC 2也采用的核心代碼BAS-INT.C這個文件。
         
        經過查看源碼發現,原來DSBasic也是基于這個版本的代碼進行擴展的。而TinyBASIC 2的功能更加強大,還支持畫圖命令(需SDL庫支持,不過SDL庫開源且跨平臺)。額外一提:自己用C/C++寫個BASIC解釋器不難,網上也有不少文章介紹,請google之。
         
        下載了TinyBASIC 2源碼后,查看BAS-INT.C文件,該文件采用了較早的C語言語法。于是首先修改語法,然后用gcc在我的Mac OS X下順利編譯通過,試著運行了幾個附帶的BASIC程序例子,一切順利。
         
        接下來便是將代碼移植到NDS上。由于devkitPro并沒有提供太多的基于命令行的NDS開發示例程序。因此需要我加一些自己的代碼來實現簡單的光標、scanf功能等。
         
        整個移植過程就不詳述了,具體可以下載后面提供的源碼。這里主要講一下,移植的幾個要點:
        (1)添加光標。我簡單的用"_",即下劃線代替光標,該光標很簡單,不會閃爍,但基本達到使用的目標,除了一個小BUG:輸入文字到行末時,會自動跳到本行行首,而不是下一行。但該Bug不影響輸入的代碼。
        (2)添加int get_input_number()函數實現INPUT命令的移植。因為我使用觸屏軟鍵盤后,NDS不支持scanf()函數從屏幕獲得輸入。
        (3)添加"RUN"和"!"兩條命令來運行程序。由于BAS-INT.C運行程序是在命令行將需要執行的BASIC程序作為命令行參數進行調用執行的,因此不支持程序編輯功能。而在NDS上我添加了一個非常簡單的程序輸入功能(包括上面提到的光標)。
         
        圖1為移植成功后的運行效果。下文將該移植到NDS的BASIC解釋器項目簡稱:NDSBasic。


        圖1. NDSBasic 運行效果。
         
        圖1為最初植移的運行界面,下方為觸屏,提供軟鍵盤進行輸入。上屏為字符終端,和DOS,以及Terminal類似。
         
        三、擴展BASIC語言解釋器
         
        該BASIC解釋器 (BAS-INT.C),提供的命令非常有限,因此需要自己擴展添加新的BASIC語言命令。由于BAS-INT.C源碼本身編寫比較清晰,添加新命令過程非常簡單。只需以下幾步完成一個新命令添加:
        (1)定義新命令宏,如 #define SEND 16
        (2)在 struct commands 結構體中添加命令的字符串,以及對應的第(1)步中的宏,如"send", SEND,
        (3)添加命令的執行函數聲明,以及函數代碼,如void exec_send();
        (4)在主函數 (main)的switch命令中添加新命令的調用,如:  
        case SEND:
        exec_send();
          break;

        如果該命令除了一般的邏輯處理外需要用到NDS硬件等功能,則可在第(3)步代碼中調用外部函數完成相應的硬件功能。這樣的設計代碼可移植性較好,邏輯功能代碼和硬件相關代碼分離。

        我主要擴展添加了以下幾條命令:
        (1)"RUN" 或 "!":如上文所述。
        (2)"LIST":打印內存里的BASIC代碼到屏幕上。
        (3)"NEW":清除內存里的BASIC代碼,開始編寫新的程序代碼。
        (4)"?":和PRINT命令一樣,用一個簡短的符號,減少輸入時間。
        (5)"SAVE filename":將當前編輯的內存里的代碼保存到filename文件中。
        (6)"LOAD filename":將filename文件里的代碼讀入內存?梢灾苯虞斎朊"RUN"或"!"運行。
        (7)"PSET x,y,clr":畫像素。在 (x,y)處像素用clr號顏色點亮。
        (8)"LINE x1,y1,x2,y2,clr [,B[F]]":從(x1,y1)到(x2,y2)用clr色號畫線、畫空心矩形、畫實心矩形命令。和QBASIC里的同名命令類似,區別是我為了方便命令的輸入,將QBASIC語法中的y1和x2之間的"-"改為了逗號","。另外,clr代表NDSBasic中的顏色號(預定義),"B"代表畫空心矩形,"BF"代表畫實心矩形。
        (9)"CIRCLE x,y,r,clr":畫圓命令。x,y代表圓心,clr為顏色號。
        (10)"DELAY ms":程序延遲ms毫秒。
        (11)"SEND":發送數據到Arduino (Slot 1接口的SPI通道)上。我將該命令的語法設計成和PRINT一致,這樣使用起來比較靈活。
        (12)"RECV n":從Arduino (Slot 1接口的SPI通道)上讀取n個字節的數據,并打印讀取到的數據。默認當讀取過程中遇到'\0'字符時也會停止讀取。
        (13)"CLS":清屏命令。執行時將清除屏幕內所有的打印信息。
         
        所有命令不分大小寫,解釋器能自動識別。實現過程中,
        • LINE命令用到了我的開源3D引擎Nomad3D中的畫線代碼,支持Cohen裁剪功能,且執行性能高效。
        • CIRCLE命令則用到了我的另一篇博文:基于NDS/GBA/ARM,從啟動到運行你自己的第一行C程序代碼(NDS篇)中的畫圓算法,效率也很高。
        • DELAY命令使用NDS的第0號硬件計時器 (Timer 0)實現,精度達到微秒級。而且每次用完就釋放計時器,不占用硬件資源。
        • SEND和RECV命令的實現用到了第三方庫:libspi-0.2 源碼,由于源碼對應的devkitPro版本太早,源碼中用的很多宏已經不存在或與當前版本(我用的是最新的版本:devkitARM r42,libnds-1.5.8)沖突。因此我重新修改了源碼并編譯成libspi.a庫文件方便以后使用。
        其中SEND命令實現的源碼如下:
        void exec_send()
        {
        //syntax: similar with PRINT
          int answer;
          int len=0, spaces;
          char last_delim;
        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);

          do {
            get_token();
            if(tok==EOL || tok==FINISHED) break;
            if(token_type==QUOTE) {
              sprintf(temp,token);
              strcat(send_str,temp);
              len += strlen(token);
              get_token();
            }
            else {
              putback();
              get_exp(&answer);
              get_token();
              len += sprintf(temp,"%d", answer);
              strcat(send_str,temp);
            }
            last_delim = *token; 

            if(*token==';') {
             
              spaces = 8 - (len % 8); 
              len += spaces;
              while(spaces) { 
        sprintf(temp," ");
        strcat(send_str,temp);
                spaces--;
              }
            }
            else if(*token==',') ;
            else if(tok!=EOL && tok!=FINISHED) serror(15); 
          } while (*token==';' || *token==',');

          if(tok==EOL || tok==FINISHED) 
          {
            if(last_delim != ';' && last_delim!=',') 
            {
            sprintf(temp,"\n");
            strcat(send_str,temp);
            }
            
             do_send(send_str,recv_buff,256);
             //printf(recv_buff);
          }
          else serror(15);
        }

        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命令實現源碼:
        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
        {
          writeBlocking_cardSPI(0x00);
          while(readBlocking_cardSPI(&read_byte) != CARD_SPI_STATUS_OK);
         
          if((char)read_byte == stop_byte)
          return i;
         
          buff[i] = (char)read_byte;
          }
        }
         
        最后,因為添加了畫圖功能,我將終端窗口從上屏移到下屏,和軟鍵盤放在一個屏內。終端窗口提供14行代碼顯示,上屏全部用來畫圖。最后運行效果如圖2, 圖3, 圖4所示。
         

        圖2. LINE命令執行效果。
         

        圖3. LINE、CIRCLE和DELAY命令在循環中執行的效果。


        圖4. 實機運行效果。

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

        相關文章

        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮