本帖最后由 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已烧录本项目的固件):
以下是项目开发过程中的软硬件图片和照片。
Demo演示照片:
Atmega328最小Arduino系统:
用于Arduino确发硬件中断给NDS的设计:
调试环境(MacOS X):
BASIC语言解释器运行效果:
一、动机
自从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的扩展电路:
图1. DS brut扩展电路
从图1中看以清楚看到除了220欧的R1电阻,发光二极管LED1,以及电源滤波的C1(100nf),没有别的电子元件。而其中R1和LED1对扩展来说不是必须,也可以去掉。因此,电路非常简单,外行都可以DIY实现。
原始的DS brut为NDS卡大小,如图2所示:
图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。
图3. 无需外部晶振,用Arduino烧写bootloader(需要修改boards.txt文件)
图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_DWRITE,SPI_COMMAND_AWRITE,SPI_COMMAND_DREAD,SPI_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和作品的合影:
|