【NO.4】DSduino-结合NDS掌机与Arduino的在线开发执行系统-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 33597|回复: 8

【NO.4】DSduino--结合NDS掌机与Arduino的在线开发执行系统

[复制链接]
发表于 2014-8-23 05:13 | 显示全部楼层 |阅读模式
本帖最后由 c_gao 于 2014-11-4 10:11 编辑

注:这是个我目前正在进行中的项目,最初项目方案发表在我的博客:Vincent(c_gao)的博客上,现将参与本次开发大赛。

摘要如下
项目名称DSduino,意为结合NDS和Adruino,提供1+1>2的功能与体验。名称为在线开发执行系统,是因为本项目移植BASIC语言到NDS,并通过NDS自带的触屏实现输入功能,即可在NDS上直接开发执行程序,和Arduino版BASIC语言不同的是,本项目输入用触屏,同时NDS的上屏用于显示程序执行结果和画图,且NDS的双ARM cpu能以更快的执行速度完成复杂的任务。
项目基础:sketch基于开源项目DS brut, 以及开源项目Arduino。核心软件与定制均由本项目完成。
预计完成时间:2014年9月。

项目目前进度已完成
1)硬件进度:硬件设计完成,无额外电子元件的最小Arduino系统在面包板上实现,PCB已在打样中;自制扩展卡完成并上传了演示DEMO。
2)软件进度带DDD调试的软件开发环境构建完成,并完成BASIC语言解释器在NDS上的移植,同时添加了20条左右新命令。支持通过SPI协议完成NDS和Arduino通信,并实现硬件中断。DEMO演示了核心的Arduino操作命令,包括NDS通过SPI控制数字口的读写,PWM的操作,以及模拟口ADC操作。实现对NDS和Arduino的命令数据包的设计与封装,以及Arduino端固件的编写。所有项目所含代码(含NDS主控端定制的BASIC解释器,以及Arduino固件代码)已上传,请从这里下载:项目完整源码打包下载。
3)文档进度:已完成方案设计与进度博文共7篇,包括详细的技术设计文档,实现原理分析和实现过程描述,每一阶段的实现均含实际项目照片和运行结果截图。详见Vincent(c_gao)的博客: http://blog.congao.net

参赛成员:Vincent (c_gao), 欢迎有基础且有兴趣的朋友加入。
联系方式Vincent (c_gao): dr.c.gao@gmail.com


项目部分图片
最后完成后的面包板DSduino硬件效果图(图中NDS端运行扩展及定制后的BASIC语言,Arduino已烧录本项目的固件): WP_20140917_004.jpg

以下是项目开发过程中的软硬件图片和照片。

Demo演示照片:
WP_20140917_008.jpg


Atmega328最小Arduino系统:
1.jpg



用于Arduino确发硬件中断给NDS的设计:
2.jpg 22.png



调试环境(MacOS X):
3.jpg 33.jpg



BASIC语言解释器运行效果:
44.jpg 444.jpg



一、动机
自从2002年拥有第一台GBA开始到2006年买的NDS,除了在两上掌机上开发程序之外,一直希望能扩展硬件做一个小车机器人。但由于当时软硬件知识的匮乏以及动手能力有限,一直没有进展。
最近无意搜到一款几年前发布的开源硬件DS brut,这是我目前了解的第二个开源NDS扩展硬件。第一个是DSerial。当时差不多2009年左右非常想买一块DSerial,可惜人在国内不方便国际邮购。如今这两个产品都已售罄,而自己感觉DIY的能力应该可以自己做一个以DS brut为基础的扩展,于是有了本篇博文。
二、背景
NDS硬件扩展主要有两种方式:(1) slot 1接口扩展 (2) slot 2接口扩展。前者为NDS卡接口,后者为GBA卡接口。slot 1接口只有17根线,而GBA接口则有32根线。两种方式扩展都有人做。

2.1  Slot 1接口扩展
Slot 1接口扩展主要有DS brut和DSerial。
通过查看DS brut和DSerial的电路图,可以了解到以下几个主要信息:(1)DS brut用的atmega168 8位单片机,
(2)DSerial用的是51系列8位单片机,
(3)两者都使用NDS的slot 1接口(NDS游戏卡接口),
(4)两者都使用SPI接口实现单片机和NDS主机的通信,
(5)两者都没有使用slot 1接口中的D0至D5数据线作为数据交换(D6,D7为SPI总线上的数据线,即MISO, MOSI),
(6)两者均使用slot 1接口的3.3V的VCC作为单片机电源,无需额外供电,
(7)两者均使用CPU内部晶振作为时钟来源,即都没使用外部晶振。

2.2  Slot 2接口扩展
在youtube上还有一个人将NDS通过slot 2接口以最简单的方式接上了Arduino,并实现通信,文章请点击这里。该方案只使用3根信号线完成通信,方案比较简单,但功能扩展也相对有限。

另一个扩展方案请点这里。该方案相对复杂,从标题来看,应该是从slot 2接口引出线后通过FTDI,即TTL串口转USB联接电脑进行通信。其实方案中只要用了TTL串口,和单片机或嵌入式板子通信就无障碍了,除了通信速率问题外。

三、方案设计
以上这些信息已基本给出NDS硬件扩展的大致方案。从容易性和扩展功能是否强大来看,Slot 2的三线扩展最容易,但功能最弱。DSerial的功能相对强大,但除了购买现成板子外,自己动手制做难度太大,而且51系列CPU开发难度相对Arduino更高。

3.1  基于DS brut的扩展
DS brut由于CPU采用Atmega168,并且提供了兼容Arduino的bootloader(从Lilypad版改良),因此单片机侧的软件开发将会非常容易,IDE和库以及文档也非常全面。再加上Atmega AVR系列单片机的最小系统非常简单,几乎只需要通电就行。因此,本方案将基于DS brut进行扩展,但不限制NDS卡带的宽度,可以将单片机外接,因此可以比DS brut有更多的GPIO和其它接口。

以下是DS brut的扩展电路:
5.jpg
图1. DS brut扩展电路
从图1中看以清楚看到除了220欧的R1电阻,发光二极管LED1,以及电源滤波的C1(100nf),没有别的电子元件。而其中R1和LED1对扩展来说不是必须,也可以去掉。因此,电路非常简单,外行都可以DIY实现。

原始的DS brut为NDS卡大小,如图2所示:
55.jpg
图2. DS brut卡实图

3.2  扩展卡制做
考虑到单独开板的成本,硬件打算简易DIY。采用现成的NDS卡,但只使用引脚部分,将引脚和上部电路(卡套盖住部分)用美工刀切断电路连接,然后分别从每个引脚焊接引线,并从卡的上部引出。如果引线后卡套盖不住,可考虑将原卡电路板上的芯片去掉(用电烙铁和吸锡器)。

3.3  Arduino部分制做
由于为了电路简单,Arduino部分电源将直接引线到NDS Slot 1的3.3V VCC。CPU理论上可以采用Atmega 48/88/168/328 系列。DS brut采用了Atmega168。采用不同的CPU在后续烧写Arduino前需要改一下Arduino IDE对应的board.txt文件,这主要是因为设置不同的CPU的Operating电压(CPU的实际运行电压)需要设置CPU内不同的fuse bit。要实现fuse bit的设置有两个方法:(1)如果使用Arduino IDE进行bootloader和程序的烧写,需要改写Arduino安装路径下的board.txt文件,添加相应的profile。(2)如果使用avrdude进行命令行烧写bootloadert和程序的烧写则可在命令行添加fuse bit的参数。但不管采用这两种方法的哪一种,fuse bit的设置参数都应是一致的。而具体设置可以参考Atmega48/88/168/328 的DataSheet文档查到。

而传统大部分Arduino开发板采用的是5V电源,本质上Atmega的大部分AVR单片机可支持1.8V至5.5V输入(具体请参考官网,点这里)。不过输入电压和CPU的工作频率是有一定相关性的,有一篇文章关于这个问题有较详细的说明(请点What if the supply is under 3.3V?以及Voltage: 3.3 vs 5)。Any way, 只要我们的Atmega单片机能够用3.3V驱动就可以了。


这里有一篇文章清楚介绍了如何设置Atmega328的fuse bits以运行电压在3.3V上工作,给出了具体实现过程:ATmega328 Fuse Bit Setting for 3.3V Vcc。



电压问题解决后,接下来就是Arduino的bootloader。DS brut定制了一个bootloader。跟据其readme文件说明,应该相对于Arduino的bootloader只是更改了一下WatchDog部分代码而以。另外DS brut是采用CPU内部晶振作为振荡源,和Lilypad一样。因此怀疑bootloader也是按Lilypad的版本改的,这部分我未验证。

关于Arduino环境中boards.txt文件(路径为:arduino版本号/hardware/arduino/boards.txt)的各个部分的解释可以参考这个官方文档:Bootloader Development。

avrdude不像Arduino先烧bootloader,然后通过bootloader用串口烧写应用程序。avrdude是个全功能的AVR烧写软件,可以烧写所有程序,包括bootloader,不过需要一个烧写硬件,如STK500或Smart ISP programmer。两种方式各有优缺点,具体可以跟据自己手上的现成硬件进行选择。avrdude的使用可以参考这篇官方教程:AVR Tutorial。


烧写bootloader的方式,DS brut给出的是采用avrdude。而本方案将采用更方便的Arduino对烧,即需要另一个Arduino成品烧写无bootloader的Atmega单片机CPU。这篇From Arduino to a Microcontroller on a Breadboard 给出了具体方案,同时这篇文章也给出了如何用另一个Arduino上传编译好的程序到独立的CPU的方法。当然如果你有ISP烧写工具,也可以直接烧写,可参考这篇文章:Minimal Arduino with ATmega8,两篇文章都给出了修改Arduino IDE的boards.txt文件的相关信息。用Arduino给CPU烧写bootloader和上传程序的接线图请见图3和图4。
555.png
图3. 无需外部晶振,用Arduino烧写bootloader(需要修改boards.txt文件)

5555.png
图4. 无需外部晶振,用Arduino给CPU上传程序(需要修改boards.txt文件)

3.4  Arduino和NDS通信设计
Arduino和NDS通信通过SPI接口完成。Arduino部分对SPI编程实现非常方便,IDE内部的有库直接支持,直接使用即可。示例代码可以参考DS brut的dsbrut_arduino.txt文件。

NDS部分则相对复杂一些,需要写驱动代码。当然DS brut也已经提供了,分别为uart, spi, brut部分。不过他们的代码比较晦涩难懂,我全部自己重新设并实现(包括设计了Arduino和NDS通信的命令封装与解封,数据格式的封装等,以及按照主控端NDS详细分析并测试和各个通信速率)。NDS部分的编译好的程序需要放到Slot 2接口的烧录卡上运行,因为Slot 1已用来扩展。NDS部分的程序开发可采用DevkitPro。libnds等库也都非常实用,可以快速开发实现功能。另外,如果NDS程序涉及到FAT文件读写别忘了打DLDI补丁就行。

NDS端的驱动部分代码可以预先编译成库,并将头文件和库文件分别放到devkitARM对应的目录里,方便以后使用。

本部分详细的内容,请参考我的博文:扩展NDS掌机连接Arduino (2)--NDS端SPI通信协议解析 以及 扩展NDS掌机连接Arduino (6)--自制NDS Slot 1扩展卡、Arduino端代码实现+简单Demo (附源码)。

四、应用
采用本方案实现NDS扩展,对于扩展出来的单片机端更新程序也方便,对于NDS端的开发和以往一样。应用实例基本上所有单片机能做的这个方案都可以实现。另外还有以下几个主要优点:
(1)NDS的主CPU为32位68MHz的ARM 9,处理能力比单片机更强,可以处理一些由单片机传回来的较为复杂的数据和运算,
(2)NDS端的程序在Slot 2,可以使用大容量的SD卡作为数据存储,
(3)NDS拥有WiFi,可以联网,有更多开发的可能性,比如远程控制,无线数据传输,
(4)NDS拥有两个LCD,可以显示很多信息,其中一个可触摸,

可以实现以下几个应用:
(1)把NDS当作电脑的游戏手柄,或电脑键盘
(2)带LCD状态显示的智能小车,可无线遥控
(3)通过NDS控制各种外设,如用触摸屏控制4*4*4,或8*8*8的LED立方显示任意图案
(4)太多了...

源码
本项目代码较多,不方便全部贴出(代码量在3000行左右)。完整项目代码下载:http://vdisk.weibo.com/s/an8qyGLMfMIFD
以下只贴出Arduino和NDS两部分的SPI通信代码。



5.1  NDS部分SPI通信代码的实现
NDS端是SPI Master,测试过程中需要Arduino端 (SPI Slave)端代码配合,以下是NDS端Send和Recv两个函数的代码。

SPI发送部分代码如下:
[mw_shl_code=cpp,true]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< max_len)
{
recv_buff = send_str;
i++;
}

recv_buff='\0';

////////
//int len=strlen(send_str);
//setupConsecutive_cardSPI(len);
_send((unsigned char*)send_str, max_len);

}
[/mw_shl_code]


以上代码中调用了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计时器硬件资源。其代码如下:


[mw_shl_code=cpp,true]
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);
}[/mw_shl_code]


SPI接收部分代码如下:
[mw_shl_code=cpp,true]
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 = (char)read_byte;
  }

  return i;
}[/mw_shl_code]

注意:由于NDS端是SPI Master,根据SPI通信原理,Slave不能主动和Master进行通信。因此,当Master需要接收数据时,需要主动发起通信请求,然后Slave接收到该请求后,就可以将相应的数据传给Master。

5.2 为NDS部分BASIC语言解释器添加4条Arduino命令
本项目为NDS和Arduino的通信和控制添加了四条命令,即DWRITE, AWRITE, DREAD, AREAD。分别为写数字引脚(类似Arduino的 digitalWrite() ),写PWM引脚(analogWrite()),读数字引脚(digitalRead()),读模拟引脚(analogRead())。首先设计了通信两端的命令格式,然后在NDS端进行命令的封装与发送,Arduino端进行解封并执行。NDS端具体实现代码如下。


(1)DWRITE 命令
格式:DWRITE pin, val。  pin为Arduino数字引脚编号;val为待写入的值,值1对应Arduino的HIGH,0对应LOW。
例如:DWRITE 6, 1
代码
[mw_shl_code=cpp,true]
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);
}[/mw_shl_code]


(2)AWRITE 命令
格式:AWRITE pin, val。  pin为Arduino带PWM功能的数字引脚编号;val为待写入的值,值范围为0~255。
例如:AWRITE 6, 110
代码
[mw_shl_code=cpp,true]
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);
}[/mw_shl_code]


(3)DREAD 命令
格式:DREAD pin, var。  pin为Arduino数字引脚编号;var为BASIC解释器内置变量,即变量字母A~Z。命令成功执行后,Arduino的pin引脚的结果将写入由var指定的BASIC解释器内置变量中。
例如:DREAD 7, J
代码
[mw_shl_code=cpp,true]
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;
}[/mw_shl_code]


(4)AREAD 命令
格式:AREAD pin, var。  pin为Arduino模拟引脚编号;var为BASIC解释器内置变量,即变量字母A~Z。命令成功执行后,Arduino的pin模拟引脚的结果将写入由var指定的BASIC解释器内置变量中。
例如:AREAD 5, H
代码

[mw_shl_code=cpp,true]
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;
}[/mw_shl_code]


说明:由于在SPI通信中,数据交换以8位,即1个字节为单位。而Arduino的ADC,即模拟引脚数据值为0~1023,即10位数据。因此读取一次Arduino的模拟引脚的数据需要2次SPI数据发送才能完成。我在Arduino端的代码实现中,将模拟引脚的数据按2次发送,先发送高字节,再发送低字节,具体参考第2.3部分内容。而上述NDS接收代码中,则做对应处理,即先接收的字节为高字节,后接收的为低字节,然后合并两个字节内容:

[mw_shl_code=cpp,true]
//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];[/mw_shl_code]


5.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。其定义如下:

[mw_shl_code=cpp,true]
#define SPI_COMMAND_DWRITE   '~'
#define SPI_COMMAND_AWRITE   '!'
#define SPI_COMMAND_DREAD   '@'
#define SPI_COMMAND_AREAD   '#'[/mw_shl_code]


第3字节:为表示pin引脚号的字节。
第4字节:为表示需要写入pin引脚的值(只适用于DWRITE,AWRITE两条命令)。


这里我使用了4个特殊字符,实际上随便使用什么字符都可以,只要保持NDS端和Arduino端定义的一致就可以。完整的Arduino封装的固件代码如下:

[mw_shl_code=cpp,true]
// DSduino firmware for arduino v1.0
// 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
[/mw_shl_code]

说明:
  • 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来演示本方案实际运行的效果。


6.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)一小张白色的纸片,可移开或盖在寻迹传感器上,以获得不同的结果。


6.2 软件配置
NDS端:将我移植扩展的最新版(下文附完整工程代码下载)BASIC解释器编译好后,复制到Mini SD卡上,并将Mini SD卡插入SuperCard Mini SD烧录卡。

6.3 Demo效果

以下为方案及DEMO的视频演示(由管理员
奈何col 添加,在此表示十分感谢):





最后,附上T-shirt和作品的合影:



WP_20141102_002.jpg

WP_20140922_002 copy.jpg
发表于 2014-8-23 11:10 | 显示全部楼层
LZ,折腾是是不是类似这个玩意啊
http://swf.com.tw/?p=382
 楼主| 发表于 2014-8-23 11:35 | 显示全部楼层
I-robofan 发表于 2014-8-23 11:10
LZ,折腾是是不是类似这个玩意啊
http://swf.com.tw/?p=382

有点类似,区别如下:
硬件:它是Slot 2接口的,我使用Slot 1接口。
软件:NDS我提供定制后的BASIC语言解释器(添加20多条命令)实现在线编程解释执行程序,并支持实时画图等功能。
发表于 2014-8-24 16:14 | 显示全部楼层
参赛项目提交要求
http://www.arduino.cn/forum.php? ... 70&fromuid=3626
(出处: Arduino中文社区)
满足提交要求后,请速来领取参赛礼物(9月1日前,提交的项目,可以领取CardBoard和Atmel T-shirt一件,10月提交的项目,可以领取Atmel T-shirt一件),完整收货地址请用参赛ID私信我。
 楼主| 发表于 2014-9-8 12:47 | 显示全部楼层
项目已制做完成,Demo、源码及系统运行照片参见我的博客http://blog.congao.net,随后按项目要求提交各材料。
 楼主| 发表于 2014-9-22 13:02 | 显示全部楼层
感谢大家支持
发表于 2014-11-11 16:40 | 显示全部楼层
为聪哥点个赞
 楼主| 发表于 2014-11-12 02:06 | 显示全部楼层

谢谢支持哈
发表于 2015-1-7 18:49 | 显示全部楼层
{:soso_e179:}{:soso_e179:}{:soso_e179:}
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-9-21 02:46 , Processed in 0.093475 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表