[转载]扩展NDS掌机连接Arduino&(6)--自制NDS&Slot&1扩展卡
前几天终于收到了在amazone上订购的电烙铁和焊锡,于是当天晚上就开工制做了Slot
1扩展卡。随后两天晚上抽空编写并测试了NDS和Arduino两部分SPI通信的代码。本篇博客将介绍三部分内容:
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个引脚,金手指和外部引脚的焊接顺序参见第一篇博文:一文中的图1)。引线长度要合适,刚好能将引出的针脚露出到卡带外部,引出部分不能太短,不然不容易接线。另外,需要将卡带底壳的上部切出7个小口,方便7个引脚的固定。这些工序完成后如图3,图4所示。
图3. 焊接好金手指部分和外部引脚效果。
图4. 焊接好金手指部分和外部引脚效果。
最后将正反面壳盖盖上,就完成了自制Slot
1扩展卡带的制做。如果卡带盖子盖得不严实,可以用胶带在外面围着粘一圈。
注意:本部分焊接要十分小心,由于金手指部分布线较密,不要将相邻的两个布线焊到一起。
二、Arduino和NDS两部分SPI通信代码的实现
在确保第一部分工作准确完成之后,接下来就需要编写代码进行SPI通信测试。
2.1 NDS部分SPI通信代码的实现
在本系列博文的第5篇:&中,已经提供了NDS端的SPI发送和接收代码,但没有进行过测试。接下来就是需要去测试它是否能正常工作。NDS端是SPI
Master,测试过程需要Arduino端 (SPI
Slave)配合,这里我只贴出完成后的NDS端Send和Recv两个函数的代码。
SPI发送部分代码如下:
_send(unsigned char* send_str, int len)
max_size = 1024;
char* p = send_
writeBlocking_cardSPI(*p);
do_delay(1);
do_send(unsigned char* send_str, unsigned char* recv_buff, int
while(send_str[i] && i& max_len)
recv_buff[i]
= send_str[i];
recv_buff[i]=' ';
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也能正常处理而不会丢失数据。do_delay()函数的实现采用NDS的Timer
0硬件计时器完成,每次函数调用时才占用该计时器,函数执行完毕便立即释放Timer 0计时器硬件资源。其代码如下:
do_delay(int millisecond)
uint ticks =
if(millisecond==-99)
timerStart(0,
ClockDivider_1024, 0, NULL);
timerElapsed(0);
fesp=ms/1000*TIMER_SPEED+ //esp =
(ticks-oldtick)/TIMER_SPEED*1000;
esp=(uint)
while(ticks
timerElapsed(0);
timerStop(0);
SPI接收部分代码如下:
do_recv(unsigned char* buff, int num_byte, unsigned char*
stop_byte)
read_byte=0;
writeBlocking_cardSPI(0x00);
while(readBlocking_cardSPI(&read_byte) !=
CARD_SPI_STATUS_OK);
& if( (NULL != stop_byte) &&
((char)read_byte == *stop_byte) )
& buff[i] =
(char)read_
注意:由于NDS端是SPI
Master,根据SPI通信原理,Slave不能主动和Master进行通信。因此,当Master需要接收数据时,需要主动发起通信请求,然后Slave接收到该请求后,就可以将相应的数据传给Master。
为NDS部分BASIC语言解释器添加4条Arduino命令
在本系列博文的第5篇:&最后,已经提到需要添加的四条命令,即DWRITE, AWRITE, DREAD,
AREAD。分别为写数字引脚(类似Arduino的 digitalWrite()
),写PWM引脚(analogWrite()),读数字引脚(digitalRead()),读模拟引脚(analogRead())。添加命令过程参见本系列第5篇博文,具体实现代码如下。
(1)DWRITE 命令
格式:DWRITE pin,
pin为Arduino数字引脚编号;val为待写入的值,值1对应Arduino的HIGH,0对应LOW。
例如:DWRITE 6, 1
exec_dwrite()
get_exp(&pin);
get_token();
if(*token !=
serror(19);
get_exp(&value);
do_dwrite(pin, value);
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
send_str[3] =
((unsigned char)(value!=0?1:0)); //value
send_str[4] =
_send(send_str, 4);
(2)AWRITE 命令
格式:AWRITE&pin,
val。& pin为Arduino带PWM功能的数字引脚编号;val为待写入的值,值范围为0~255。
例如:AWRITE&6, 110
exec_awrite()
get_exp(&pin);
get_token();
if(*token !=
serror(20);
get_exp(&value);
do_awrite(pin, value);
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
send_str[3] =
(unsigned char)(value); //value
send_str[4] =
_send(send_str, 4);&
(3)DREAD 命令
格式:DREAD&pin,
var。& pin为Arduino数字引脚编号;var为BASIC解释器内置变量,即变量字母A~Z。命令成功执行后,Arduino的pin引脚的结果将写入由var指定的BASIC解释器内置变量中。
例如:DREAD&7, J
exec_dread()
int pin, var,
get_exp(&pin);
get_token();
if(*token !=
serror(21);
get_token();&
toupper(*token)-'A';
do_dread(pin);
& variables[var] =
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
send_str[3] =
_send(send_str, 3);
do_recv(&value,1,NULL);
//printf("recv:%dn",value);
(4)AREAD 命令
格式:AREAD&pin,
var。& pin为Arduino模拟引脚编号;var为BASIC解释器内置变量,即变量字母A~Z。命令成功执行后,Arduino的pin模拟引脚的结果将写入由var指定的BASIC解释器内置变量中。
例如:AREAD&5, H
exec_aread()
int pin, var,
get_exp(&pin);
get_token();
if(*token !=
serror(22);
get_token();&
toupper(*token)-'A';
do_aread(pin);
& variables[var] =
do_aread(int pin)
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
send_str[3] =
_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
recv_str[0];&
recv_str[1];
说明:由于在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
recv_str[0];&
recv_str[1];
2.3 Arduino部分SPI通信代码的实现
Arduino部分代码主要完成两部分功能:
(1)配置Arduino为SPI Slave端;
(2)接收NDS端发送过来的SPI命令,并解析执行命令。
第(1)部分功能的详细分析过程详见本系列博文2:。而第(2)部分中,我将NDS发送的命令进行了简单的封装(类似SD卡读写命令的原理一样)。命令的格式采用如下形式:
第1字节:'\',为命令起始标志字节。
第2字节:为表示具体命令的字节。例如本篇上述2.2小节内容中NDS封装了4条操作Arduino的命令,分别使用SPI_COMMAND_DWRITE,SPI_COMMAND_AWRITE,SPI_COMMAND_DREAD,SPI_COMMAND_AREAD。其定义如下:
SPI_COMMAND_DWRITE
SPI_COMMAND_AWRITE
SPI_COMMAND_DREAD
SPI_COMMAND_AREAD
这里我使用了4个特殊字符,实际上随便使用什么字符都可以,只要保持NDS端和Arduino端定义的一致就可以。完整的Arduino封装代码如下:
// Written by
Vincent Gao (c_gao)
http://blog.congao.net
"pins_arduino.h"
//#include
#define SS 10
& & // PB2
#define MOSI
#define MISO
#define SCK
// what to do
with incoming data
byte command
byte led_pin
led_status = 1;
& Serial.begin(9600);
& // setup SPI interface
& pinMode(SS, INPUT);
& pinMode(MOSI, INPUT);
& pinMode(MISO, OUTPUT);
& pinMode(SCK, INPUT);
& // enable SPI interface, CPOL=1,
(1&&6)|(1&&3)|(1&&2);
& // dummy read
& clr = SPSR;
& clr = SPDR;
& //attachInterrupt (0, ss_falling,
& //SPI.attachInterrupt();
spi_trans(volatile byte out)
& // send and receive a character,
& while (!(SPSR &
& return SPDR;
SPI_COMMAND_DWRITE '~'
SPI_COMMAND_AWRITE '!'
SPI_COMMAND_DREAD '@'
SPI_COMMAND_AREAD '#'
is_recvdata =
is_command =
wait_command_info =
wait_num_byte = 0;
int index =
do_spi(volatile byte out)
& while (!(SPSR &
& byte d = SPDR;
& //Serial.write(d);
& if(d == '\') // it is a
& & is_command =
& & index =
& & wait_command_info =
& & return
& if(is_command)
& & is_command =
& & buf[index++] =
& & wait_command_info =
case SPI_COMMAND_DWRITE:
case SPI_COMMAND_AWRITE:
& wait_num_byte = 2;
case SPI_COMMAND_DREAD:
case SPI_COMMAND_AREAD:
& wait_num_byte = 1;
& wait_num_byte = 0;
& & return
& 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 =
//Serial.println("AAA");
switch(buf[0])
& case SPI_COMMAND_DWRITE:
& & pin = buf[1];
& & value = buf[2];
& & //buf[3]=0;
//Serial.println(buf);
& & pinMode(pin,
& & digitalWrite(pin,
& case SPI_COMMAND_AWRITE:
& & pin = buf[1];
& & value = buf[2];
& & pinMode(pin,
& & analogWrite(pin,
& case SPI_COMMAND_DREAD:
& & pin = buf[1];
& & pinMode(pin,
& & value =
digitalRead(pin);
& & //SPDR = 0xff &
& & spi_trans(0xff &
//Serial.println(value);
& case SPI_COMMAND_AREAD:
& & pin = buf[1];
& & pinMode(pin,
& & value =
analogRead(pin);
spi_trans(value&&8);
& & spi_trans(0xff &
& default:
& & return
spi_transfer(volatile byte out)
(SPI_STC_vect)
& static byte sss=LOW;
& while (!(SPSR &
& byte d = SPDR;
& Serial.write(d);
& if(d == 'A')
if(sss==LOW)
digitalWrite(led_pin, HIGH);
digitalWrite(led_pin, LOW);
& return 1;
& //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 硬件配置
(1)我使用初版NDS;
(2)SuperCard Mini SD烧录卡+1GB mini
SD卡+读卡器;
(3)上文自制好的扩展卡,并插入NDS主机的Slot
Arduino端:
(1)面包板上的最小Arduino系统(详见本系列第1篇博文:),并用杜绑线和自制的NDS扩展卡并连接好;
(2)LED灯,连接至Arduino的数字引脚
6,该引脚同时也是PWM引脚;
(3)寻迹传感器(数字传感器),连接至Arduino的数字引脚
(4)土壤湿度传感器(模拟传感器),连接至Arduino的模拟引脚
(5)无CPU 的Arduino
UNO板,用于上传Arduino程序。
(1)用水打湿的餐巾纸,用于裹在土壤湿度传感器的感应片上,以获得不同的湿度结果;
(2)一小张白色的纸片,可移开或盖在寻迹传感器上,以获得不同的结果。
3.2 软件配置
NDS端:将我移植扩展的最新版(下文附完整工程代码下载)BASIC解释器编译好后,复制到Mini SD卡上,并将Mini
SD卡插入SuperCard Mini SD烧录卡。
Arduino端:将上文的完整Arduino代码(下文附完整工程代码下载)编译并上传至最小Arduino系统。
启动NDS,并从SuperCard Mini
SD卡运行BASIC解析器。首先输入下行命令并回车:
DWRITE 6, 1
然后输入"!"或"RUN",回车后如果LED灯被点亮,说明硬件连接正确无异常,如图5所示。
图5. NDS通过BASIC解释器发送DWRITE命令点亮Arduino的LED。
然后,在NDS端输入以下一段BASIC程序:
FOR I=0 TO
& AWRITE 6, I
& DREAD 7, J
& PRINT "digit pin 7:", J
& AREAD 5, J
& PRINT "analog pin 5:", J
& DELAY 1000
然后输入"!"或"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的Slot
1只提供3.3V电压,而我手上的超声波传感器在该电压下工作不正常,能返回数据,但不正确,到我外接5V电压时则就变得正确。
全部工程代码(含NDS端BASIC解释器代码,以及Arduino代码):
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。