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

    擴展NDS掌機連接Arduino (6)-自制NDS Slot 1擴展卡、Arduino端代碼實現+簡單Demo (附源碼)

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

        前幾天終于收到了在amazone上訂購的電烙鐵和焊錫,于是當天晚上就開工制做了Slot 1擴展卡。隨后兩天晚上抽空編寫并測試了NDS和Arduino兩部分SPI通信的代碼。順便打個廣告:有興趣的朋友可以加入QQ群:362445156 (Arduino極客群)。

     
    本篇博客將介紹三部分內容:
    • Slot 1擴展卡的自制過程;
    • Arduino和NDS兩部分SPI通信代碼實現;
    • 通過簡單Demo演示應用效果。
     
    最后完成后的效果見圖0:

    圖0. 最后制做完成并連接后的效果。
     
    一、Slot 1擴展卡的自制過程
    Slot 1擴展卡的自制,我用了兩種方案:(1)用開源DS brut項目的PCB來打樣擴展卡;(2)用正版NDS Slot 1游戲卡手工制做擴展卡。這兩種方案我都用了,但由于人在國外,PCB打樣在淘寶上找的商家,不方便寄到國外。這里主要講第二種方案,即用已有的正版游戲卡制做Slot 1擴展卡的過程。
     
    首先去Walmart買了一張最廉價的NDS游戲,才$8,如圖1,圖2。

    圖1. 全新的NDS游戲等待拆解。
     

    圖2. 打開盒子,游戲卡在右邊。
     
    然后將卡帶取出,并將插入NDS主機卡槽的金手指部分用美工刀切下,并焊接好引出到外部的針腳(一共只需焊接7個引腳,金手指和外部引腳的焊接順序參見第一篇博文:擴展NDS掌機連接Arduino方案設計一文中的圖1)。引線長度要合適,剛好能將引出的針腳露出到卡帶外部,引出部分不能太短,不然不容易接線。另外,需要將卡帶底殼的上部切出7個小口,方便7個引腳的固定。這些工序完成后如圖3,圖4所示。

    圖3. 焊接好金手指部分和外部引腳效果。
     

    圖4. 焊接好金手指部分和外部引腳效果。
     
    最后將正反面殼蓋蓋上,就完成了自制Slot 1擴展卡帶的制做。如果卡帶蓋子蓋得不嚴實,可以用膠帶在外面圍著粘一圈。
     
    注意:本部分焊接要十分小心,由于金手指部分布線較密,不要將相鄰的兩個布線焊到一起。
     
    二、Arduino和NDS兩部分SPI通信代碼的實現
    在確保第一部分工作準確完成之后,接下來就需要編寫代碼進行SPI通信測試。
     
    2.1 NDS部分SPI通信代碼的實現
    在本系列博文的第5篇:擴展NDS掌機連接Arduino (5)--NDS端BASIC語言解釋器的移植與擴展 中,已經提供了NDS端的SPI發送和接收代碼,但沒有進行過測試。接下來就是需要去測試它是否能正常工作。NDS端是SPI Master,測試過程需要Arduino端 (SPI Slave)配合,這里我只貼出完成后的NDS端Send和Recv兩個函數的代碼。
     
    SPI發送部分代碼如下:
    void _send(unsigned char* send_str, int len)
    {
    int i=0, max_size = 1024;
    unsigned char* p = send_str;
    while(i
    {
    writeBlocking_cardSPI(*p);
    do_delay(1);
    p++;
    i++;
    }
    }

    void do_send(unsigned char* send_str, unsigned 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);
    //setupConsecutive_cardSPI(len);
    _send((unsigned char*)send_str, max_len);
     
    }
     
    以上代碼中調用了do_delay(1)函數,意即NDS每發送1個字節的數據就等待1ms。NDS需要等待因為NDS的執行速度遠比Arduino快,如果NDS只發送不等待,會使得Arduino無法處理接收數據。而通過我不斷測試,NDS等待1ms的時間比較保險,在大量數據傳送過程中不致于Arduino處理不過來。實際上,我也測試過NDS等待0.1ms, 0.5ms待不同的值,在數據量較小的情況下(10字節以內),Arduino也能正常處理而不會丟失數據。本段文字描述的延時都是在Arduino通過串口傳回結果的前提下進行測試,而Arduino操作串口通信會消耗大量處理的時間,因此如果不使用串口顯示結果,延時可以做到非常小。比如和一個網友交流過這個問題,他使用Slot 2接口可以做到512kbs的SPI通信速度,而Slot 1可以提供更高的速度,至少也可以做到這個速度。
    上面代碼中的do_delay()函數的實現采用NDS的Timer 0硬件計時器完成,每次函數調用時才占用該計時器,函數執行完畢便立即釋放Timer 0計時器硬件資源。其代碼如下:
     
    void do_delay(int millisecond)
    {
    uint ticks = 0;
    uint oldtick;
    double ms=millisecond;
    if(millisecond==-99)
    ms=0.5;
     
    timerStart(0, ClockDivider_1024, 0, NULL);
     
    ticks += timerElapsed(0);
    oldtick = ticks;
     
    double fesp=ms/1000*TIMER_SPEED+oldtick; //esp = (ticks-oldtick)/TIMER_SPEED*1000;
    uint esp=(uint)fesp;
    while(ticks
    ticks += timerElapsed(0);
     
    timerStop(0);
    }
     
    SPI接收部分代碼如下:
    int do_recv(unsigned char* buff, int num_byte, unsigned 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( (NULL != stop_byte) && ((char)read_byte == *stop_byte) )
      return i;
     
      buff[i] = (char)read_byte;
      }
      
      return i;
    }
     
    注意:由于NDS端是SPI Master,根據SPI通信原理,Slave不能主動和Master進行通信。因此,當Master需要接收數據時,需要主動發起通信請求,然后Slave接收到該請求后,就可以將相應的數據傳給Master。
     
    2.2 為NDS部分BASIC語言解釋器添加4條Arduino命令
    在本系列博文的第5篇:擴展NDS掌機連接Arduino (5)--NDS端BASIC語言解釋器的移植與擴展 最后,已經提到需要添加的四條命令,即DWRITE, AWRITE, DREAD, AREAD。分別為寫數字引腳(類似Arduino的 digitalWrite() ),寫PWM引腳(analogWrite()),讀數字引腳(digitalRead()),讀模擬引腳(analogRead())。添加命令過程參見本系列第5篇博文,具體實現代碼如下。

    (1)DWRITE 命令
    格式:DWRITE pin, val。  pin為Arduino數字引腳編號;val為待寫入的值,值1對應Arduino的HIGH,0對應LOW。
    例如:DWRITE 6, 1
    代碼
    void exec_dwrite()
    {
    int pin, value;
    get_exp(&pin);
     
    get_token();
    if(*token != ',') 
    serror(19);
     
    get_exp(&value);
     
    do_dwrite(pin, value);
    }
     
    void do_dwrite(int pin, int value)
    {
    unsigned char send_str[5];
     
    send_str[0] = '\\'; //command sign
    send_str[1] = SPI_COMMAND_DWRITE; //command type
    send_str[2] = (unsigned char)pin; //pin
    send_str[3] = ((unsigned char)(value!=0?1:0)); //value
    send_str[4] = '\0';
    _send(send_str, 4);
    }
     
    (2)AWRITE 命令
    格式AWRITE pin, val。  pin為Arduino帶PWM功能的數字引腳編號;val為待寫入的值,值范圍為0~255。
    例如AWRITE 6, 110
    代碼
    void exec_awrite()
    {
    int pin, value;
    get_exp(&pin);
     
    get_token();
    if(*token != ',') 
    serror(20);
     
    get_exp(&value);
     
    do_awrite(pin, value);
    }

    void do_awrite(int pin, int value)
    {
    unsigned char send_str[5];
     
    send_str[0] = '\\'; //command sign
    send_str[1] = SPI_COMMAND_AWRITE; //command type
    send_str[2] = (unsigned char)pin; //pin
    send_str[3] = (unsigned char)(value); //value
    send_str[4] = '\0';
    _send(send_str, 4); 
    }
     
    (3)DREAD 命令
    格式DREAD pin, var。  pin為Arduino數字引腳編號;var為BASIC解釋器內置變量,即變量字母A~Z。命令成功執行后,Arduino的pin引腳的結果將寫入由var指定的BASIC解釋器內置變量中。
    例如DREAD 7, J
    代碼
    void exec_dread()
    {
    int pin, var, data;
     
    get_exp(&pin);
     
    get_token();
    if(*token != ',') 
    serror(21);

      get_token(); 
      var = toupper(*token)-'A';

    data = do_dread(pin);

      variables[var] = data;
    }

    int do_dread(int pin)
    {
    unsigned char value=0x0;
    unsigned char send_str[4];
     
    send_str[0] = '\\'; //command sign
    send_str[1] = SPI_COMMAND_DREAD; //command type
    send_str[2] = (unsigned char)pin; //pin
    send_str[3] = '\0';
     
    _send(send_str, 3);
     
    do_recv(&value,1,NULL);
    //printf("recv:%d\n",value);
    return value;
    }
     
    (4)AREAD 命令
    格式AREAD pin, var。  pin為Arduino模擬引腳編號;var為BASIC解釋器內置變量,即變量字母A~Z。命令成功執行后,Arduino的pin模擬引腳的結果將寫入由var指定的BASIC解釋器內置變量中。
    例如AREAD 5, H
    代碼
    void exec_aread()
    {
    int pin, var, data;
     
    get_exp(&pin);
     
    get_token();
    if(*token != ',') 
    serror(22);

      get_token(); 
      var = toupper(*token)-'A';

    data = do_aread(pin);

      variables[var] = data;
    }

    int do_aread(int pin)
    {
    int value;
    unsigned char recv_str[2];
    unsigned char send_str[4];
     
    send_str[0] = '\\'; //command sign
    send_str[1] = SPI_COMMAND_AREAD; //command type
    send_str[2] = (unsigned char)pin; //pin
    send_str[3] = '\0';
     
    _send(send_str, 3);
     
    recv_str[0]=recv_str[1]=0;
    do_recv(recv_str,2,NULL);
     
    //the first byte send from arduino is the high byte of the result of analog read.
    value = recv_str[0]; 
    value <<= 8;
    value |= recv_str[1];
     
    return value;
    }
     
    說明:由于在SPI通信中,數據交換以8位,即1個字節為單位。而Arduino的ADC,即模擬引腳數據值為0~1023,即10位數據。因此讀取一次Arduino的模擬引腳的數據需要2次SPI數據發送才能完成。我在Arduino端的代碼實現中,將模擬引腳的數據按2次發送,先發送高字節,再發送低字節,具體參考第2.3部分內容。而上述NDS接收代碼中,則做對應處理,即先接收的字節為高字節,后接收的為低字節,然后合并兩個字節內容:
     
    //the first byte send from arduino is the high byte of the result of analog read.
    value = recv_str[0]; 
    value <<= 8;
    value |= recv_str[1];
     
    2.3 Arduino部分SPI通信代碼的實現
    Arduino部分代碼主要完成兩部分功能:
    (1)配置Arduino為SPI Slave端;
    (2)接收NDS端發送過來的SPI命令,并解析執行命令。
    第(1)部分功能的詳細分析過程詳見本系列博文2:擴展NDS掌機連接Arduino (2)--NDS端SPI通信協議解析。而第(2)部分中,我將NDS發送的命令進行了簡單的封裝(類似SD卡讀寫命令的原理一樣)。命令的格式采用如下形式:
    第1字節:'\\',為命令起始標志字節。
    第2字節:為表示具體命令的字節。例如本篇上述2.2小節內容中NDS封裝了4條操作Arduino的命令,分別使用SPI_COMMAND_DWRITE,SPI_COMMAND_AWRITE,SPI_COMMAND_DREAD,SPI_COMMAND_AREAD。其定義如下:
     
    #define SPI_COMMAND_DWRITE   '~'
    #define SPI_COMMAND_AWRITE   '!'
    #define SPI_COMMAND_DREAD   '@'
    #define SPI_COMMAND_AREAD   '#'
     
    第3字節:為表示pin引腳號的字節。
    第4字節:為表示需要寫入pin引腳的值(只適用于DWRITE,AWRITE兩條命令)。

    這里我使用了4個特殊字符,實際上隨便使用什么字符都可以,只要保持NDS端和Arduino端定義的一致就可以。完整的Arduino封裝代碼如下:
     
    // Written by Vincent Gao (c_gao)
    //BLOG: http://blog.congao.net
    //EMAIL: dr.c.gao@gmail.com
    // Sep. 2014

    #include "pins_arduino.h"
    //#include "SPI.h"
    #define SS 10                 // PB2
    #define MOSI 11               // PB3
    #define MISO 12               // PB4
    #define SCK 13                // PB5

    // what to do with incoming data
    byte command = 0;
    byte led_pin = 6;
    byte led_status = 1;

    void setup()
    {
      byte clr;
      
      Serial.begin(9600);
      
      // setup SPI interface
      pinMode(SS, INPUT);
      pinMode(MOSI, INPUT);
      pinMode(MISO, OUTPUT);
      pinMode(SCK, INPUT);
      
      // enable SPI interface, CPOL=1, CHPA=1
      SPCR = (1<<6)|(1<<3)|(1<<2);
      // dummy read
      clr = SPSR;
      clr = SPDR;
      
      //attachInterrupt (0, ss_falling, FALLING);
      //SPI.attachInterrupt();
    }

    byte spi_trans(volatile byte out)
    {
      // send and receive a character, blocking
      SPDR = out;
      while (!(SPSR & (1<<7)));
      return SPDR;
    }

    #define SPI_COMMAND_DWRITE '~'
    #define SPI_COMMAND_AWRITE '!'
    #define SPI_COMMAND_DREAD '@'
    #define SPI_COMMAND_AREAD '#'
    boolean is_recvdata = false;
    boolean is_command = false;
    boolean wait_command_info = false;
    int wait_num_byte = 0;
    char buf[4];
    int index = 0;
    int pin;
    int value;

    byte do_spi(volatile byte out)
    {
      SPDR = out;
      while (!(SPSR & (1<<7)));
      byte d = SPDR;
      //Serial.write(d);

      if(d == '\\') // it is a command
      {
        is_command = true;
        index = 0;
        wait_command_info = false;
        return d;
      }
      
      if(is_command)
      {
        is_command = false;
        buf[index++] = d;
        wait_command_info = true;
        switch(d)
        {
          case SPI_COMMAND_DWRITE:
          case SPI_COMMAND_AWRITE:
            wait_num_byte = 2;
            break;
          case SPI_COMMAND_DREAD:
          case SPI_COMMAND_AREAD:
            wait_num_byte = 1;
            break;
          default:
            wait_num_byte = 0;
            break;
        }
        return d;
      }
      
      if(wait_command_info && (wait_num_byte > 0))
      {
        buf[index++]=d;
        wait_num_byte--;
        //Serial.print(wait_num_byte);
      }
      
      if(wait_command_info && (wait_num_byte == 0))
      {
          //deal with command
          index = 0;
          wait_command_info = false;
          //Serial.println("AAA");
          switch(buf[0])
          {
            case SPI_COMMAND_DWRITE:
              pin = buf[1];
              value = buf[2];
              //buf[3]=0;
              //Serial.println(buf);
              pinMode(pin, OUTPUT);
              digitalWrite(pin, value);
              break;
            case SPI_COMMAND_AWRITE:
              pin = buf[1];
              value = buf[2];
              pinMode(pin, OUTPUT);
              analogWrite(pin, value);
              break;
            case SPI_COMMAND_DREAD:
              pin = buf[1];
              pinMode(pin, INPUT);
              value = digitalRead(pin);
              //SPDR = 0xff & value;
              spi_trans(0xff & value);
              //Serial.println(value);
              break;
            case SPI_COMMAND_AREAD:
              pin = buf[1];
              pinMode(pin, INPUT);
              value = analogRead(pin);
              spi_trans(value>>8);
              spi_trans(0xff & value);
              break;
            default:
              break;
          }
        return d;
      }
        
      return d;
    }

    byte spi_transfer(volatile byte out)
    //ISR (SPI_STC_vect)
    {
      static byte sss=LOW;
      
      SPDR = out;
      while (!(SPSR & (1<<7)));
      byte d = SPDR;
      
      Serial.write(d);

      if(d == 'A')
      {
        if(sss==LOW)
        {
          digitalWrite(led_pin, HIGH);
          sss=HIGH;
        }
        else
        {
          digitalWrite(led_pin, LOW);
          sss=LOW;
        }
      }    
      return 1;
    }
     

    void loop (void)
    {
      //spi_transfer(0xee);
      do_spi(0xee);
      //Serial.println("A");
      //pinMode(7,INPUT);
      //Serial.println(digitalRead(7));
    }  // end of loop
     
    說明:
    • setup()函數完成對Arduino端SPI Slave模式的配置,并配置SPI通信模式為Mode 3(CPOL=1, CHPA=1)。
    • byte do_spi(volatile byte out)函數為主體功能函數,該函數實現對發送過來的4條SPI命令進行解析和執行。
    • byte spi_trans(volatile byte out)函數實現向NDS發送數據。
    • byte spi_transfer(volatile byte out)函數是之前用于測試SPI時使用,最后沒有使用,也沒有刪除。
    三、Demo演示應用效果
    這部分內容,我使用一個簡單的Demo來演示本方案實際運行的效果。
     
    3.1 硬件配置
    NDS端
    (1)我使用初版NDS;
    (2)SuperCard Mini SD燒錄卡+1GB mini SD卡+讀卡器;
    (3)上文自制好的擴展卡,并插入NDS主機的Slot 1卡槽。
     
    Arduino端
    (1)面包板上的最小Arduino系統(詳見本系列第1篇博文:擴展NDS掌機連接Arduino (1)--Arduino端最小系統實現),并用杜綁線和自制的NDS擴展卡并連接好;
    (2)LED燈,連接至Arduino的數字引腳 6,該引腳同時也是PWM引腳;
    (3)尋跡傳感器(數字傳感器),連接至Arduino的數字引腳 7;
    (4)土壤濕度傳感器(模擬傳感器),連接至Arduino的模擬引腳 5;
    (5)無CPU 的Arduino UNO板,用于上傳Arduino程序。
     
    其它
    (1)用水打濕的餐巾紙,用于裹在土壤濕度傳感器的感應片上,以獲得不同的濕度結果;
    (2)一小張白色的紙片,可移開或蓋在尋跡傳感器上,以獲得不同的結果。
     
    3.2 軟件配置
    NDS端:將我移植擴展的最新版(下文附完整工程代碼下載)BASIC解釋器編譯好后,復制到Mini SD卡上,并將Mini SD卡插入SuperCard Mini SD燒錄卡。
     
    Arduino端:將上文的完整Arduino代碼(下文附完整工程代碼下載)編譯并上傳至最小Arduino系統。
     
    3.3 Demo
    啟動NDS,并從SuperCard Mini SD卡運行BASIC解析器。首先輸入下行命令并回車:
    DWRITE 6, 1
    然后輸入"!"或"RUN",回車后如果LED燈被點亮,說明硬件連接正確無異常,如圖5所示。

    圖5. NDS通過BASIC解釋器發送DWRITE命令點亮Arduino的LED。
     
    然后,在NDS端輸入以下一段BASIC程序:
     
    FOR I=0 TO 255
      AWRITE 6, I
      DREAD 7, J
      PRINT "digit pin 7:", J
      AREAD 5, J
      PRINT "analog pin 5:", J
      DELAY 1000
    NEXT
     
    然后輸入"!"或"RUN",回車后這段BASIC程序便開始執行,運行結果會輸出在下屏,并按每秒一次進行更新顯示,結果如圖6,圖7所示:

    圖6. 用紙片蓋住尋跡傳感器(傳感器燈變紅),而濕度傳感器則不接觸濕餐巾紙,則相應的NDS屏上輸出:digit pin 7: 1      analog pin 5: 1023。結果正確。

    圖7. 將紙片從尋跡傳感器移開(傳感器燈變綠),而濕度傳感器用濕餐巾紙包裹住,則相應的NDS屏上輸出:digit pin 7: 0      analog pin 5: 728。并且值在不停刷新,結果正確。
    至此,該項目核心的軟件和硬件都基本完成了,F在只需要發揮人的想象力便可用它制做出各種有趣的作品,比如:
    • 尋跡小車。將本項目的NDS和Arduino固定在小車底座上,用3個尋跡傳感器通過DREAD命令返回數據給NDS,并決定左右轉彎或直走的決策,然后用AWRITE命令驅動連接在PWM引腳上的電機驅動板即可實現。同時配合由我添加的BASIC畫圖命令可以實現在NDS上屏畫出上車的運動軌跡;驅⑿≤囘\動時的速度,位置等信息記錄下來存到NDS燒錄卡上的SD卡內,以做后續析和處理。
    • 萬用表。萬用表功能非常實用,利用本方案實現上卻并不復雜。Arduino端外接兩條杜邦線作為測筆,通過一小段萬用表代碼即可實現。
    • GPS記錄儀。將本項目外接一個GPS模塊,將GPS的經緯度信息回傳到NDS,可實現將你的運動行蹤記錄到SD卡內,或畫到屏幕上。如果你編程不錯,還可以直接在NDS上寫個小程序將離線地圖顯示在NDS上,并把經緯度坐標顯示在該地圖上,然后你可以帶上這個NDS出門跑步,開車,騎車,回來后就可以看到你的行蹤軌跡了。
    • N路邏輯分析儀。Arduino端如果使用atmega328,它提供了13個數字引腳,理論上最多可以作13路邏輯分析儀,不過按上文對SPI通信速率的分析和實驗,預計頻率只能做到1MHz左右。
    • N路示波器。原理同N路邏輯分析儀,可使用NDS上屏顯示波型。
    • 家庭環境監視機。歸功于NDS自帶的WiFi功能,使用libwifi庫,可將Arduino端連接的各種傳感器數據,如溫度,濕度,氣壓,PM2.5值,一氧化碳指數等信息傳到遠程服務器。例如國內免費的Yeelink云平臺,這樣你就可以隨時隨地查看家里的情況了。話說,3DS homebrew channel馬上就要開放了,因此還可以用3DS上的攝像頭拍的照片也上傳上去,遠程監視家里的情況。
    • 還有很多...
     
    后記:
    我也測試了超聲波傳感器,但由于NDS的Slot 1只提供3.3V電壓,而我手上的超聲波傳感器在該電壓下工作不正常,能返回數據,但不正確,到我外接5V電壓時則就變得正確。
     
    全部工程代碼(含NDS端BASIC解釋器代碼,以及Arduino代碼):http://www.thefapper.com/f/NDS擴展Arduino源碼.zip
    關閉窗口

    相關文章

    亚洲一区二区制服在线|在绩专区欧美自拍日韩|青春娱乐网97超碰人人射|在线观看国产网址你懂的