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

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> 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
        關閉窗口

        相關文章

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