久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

專注電子技術學習與研究
當前位置:單片機教程網 >> 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_DWRITESPI_COMMAND_AWRITESPI_COMMAND_DREADSPI_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。并且值在不停刷新,結果正確。
至此,該項目核心的軟件和硬件都基本完成了。現在只需要發揮人的想象力便可用它制做出各種有趣的作品,比如:
  • 尋跡小車。將本項目的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.zg4o1577.cn/f/NDS擴展Arduino源碼.zip
關閉窗口

相關文章

主站蜘蛛池模板: 99久久国产免费 | 精品国产91乱码一区二区三区 | 久久日本| 色婷婷精品国产一区二区三区 | 国产精品久久久久久久久婷婷 | 久草中文在线 | 二区亚洲 | 色视频在线播放 | 亚洲97 | 欧美一区在线视频 | 2018中文字幕第一页 | 精品福利视频一区二区三区 | 国产精品久久久久久一区二区三区 | 毛片入口 | 天天av网 | 成人国产精品久久久 | 美女高潮网站 | 日韩欧美专区 | 午夜爽爽男女免费观看hd | 国产探花在线精品一区二区 | 一区二区电影 | 99re超碰| 伊人伊人 | chinese中国真实乱对白 | 极品一区 | 成人在线免费 | 日本欧美国产 | 日韩中文av在线 | 激情五月婷婷在线 | 天天操网| 成人午夜av| 黄色一级免费看 | 国产专区在线 | 国外激情av | 欧美一区精品 | 成人不卡在线 | 亚洲性免费| 亚洲 日本 欧美 中文幕 | 国产精品18久久久 | 激情五月婷婷综合 | 天天操网 |