專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> Arduino >> 瀏覽文章

擴(kuò)展NDS掌機(jī)連接Arduino (2)-NDS端SPI通信協(xié)議解析

作者:c_gao   來(lái)源:轉(zhuǎn)自c_gao   點(diǎn)擊數(shù):  更新時(shí)間:2014年07月05日   【字體:

本系列上一篇文章介紹了如何構(gòu)建一個(gè)最小Arduino系統(tǒng),這為擴(kuò)展NDS做好了Arduino端的硬件準(zhǔn)備。接下來(lái)的工作:

 
(1)硬件部分:主要為NDS端Slot 1卡的改造以及引線連接這個(gè)最小Arduino系統(tǒng);
(2)軟件部分:主要為兩端SPI通信的實(shí)現(xiàn),以及NDS端通信部分軟件架構(gòu)和API封裝;
(3)技術(shù)演示:一個(gè)Demo。
 
由于上述第(1)部分需要的部分工具和材量目前還沒(méi)到位,因此本篇先介紹第二部分的SPI通信協(xié)議解析。以下內(nèi)容首先基本介紹SPI通信協(xié)議,然后針對(duì)NDS硬件詳細(xì)介紹如何開(kāi)發(fā)使用NDS的Slot 1硬件接口部分的SPI協(xié)議。最后簡(jiǎn)單介紹如何實(shí)現(xiàn)Arduino作為Slave端的SPI如何實(shí)現(xiàn)。
 
一、什么是SPI ?
SPI是Serial Peripheral Interface的縮寫(xiě),意即串口外圍接口,它與UART,IIC一樣是單片機(jī)和嵌入式通信的重要協(xié)議。SPI最早由Motorola開(kāi)發(fā),它是一種異步,串行,主從模式,全雙工的通信協(xié)議。有許多外圍設(shè)備使用SPI協(xié)議工作,比如我在STM32F4 Discovery + FreeRTOS + 中文字庫(kù) + 12864LCD一文中使用的12864 OLED顯示屏就使用SPI總線。
 
SPI可以一主(Master)一從(Slave)模式工作,也可以一主多從模式工作,但后者同一時(shí)刻只有一個(gè)從機(jī)與主機(jī)通信。一主一從模式工作如圖1所示:

圖1. SPI總線以一主一從模式工作時(shí)的連線。
其中:
(1) SCLK (Serial Clock) 為時(shí)鐘,由Master提供,
(2) MOSI (Master Output, Slave Input) 為Master向Slave端單向傳輸數(shù)據(jù)的通道,
(3) MISO (Master Input, Slave Output)  為Slave向Master端單向傳輸數(shù)據(jù)的通道,
(4) SS (Slave Select) 為Slave選中信號(hào),低電平時(shí)表示選中,高電平時(shí)表示不選中。

特別要注意的一點(diǎn)是連線方式和UART串口不同,在SPI連線中,MOSI始終連MOSI,MISO始終連MISO,無(wú)需交叉連接

SPI協(xié)議規(guī)范比較松散,因此有很多變種。比如有三線模式SPI,它將MOSI與MISO合并為一線,提供半雙工工作模式,即同時(shí)只能有一個(gè)方向的數(shù)據(jù)傳輸,優(yōu)點(diǎn)是線少(只需3根),缺點(diǎn)是半雙工,數(shù)據(jù)不能雙向同時(shí)傳輸。其它還有多數(shù)據(jù)線模式SPI等,在此不作詳述。

二、SPI如何工作?
關(guān)于SPI的工作原理,這里有一篇文章圖文結(jié)合,講得非常詳細(xì):SPI - Serial Peripheral Interface - for Arduino。這里我摘主要內(nèi)容來(lái)講一下。
 

圖2. 邏輯分析儀的SPI通信時(shí)序圖。
 
圖2用邏輯分析儀截取了一段SPI通信的時(shí)序圖。結(jié)合該圖分析SPI原理會(huì)非常清楚。
 
首先,當(dāng)SS為A區(qū)域(高電平)時(shí),不進(jìn)行通信。當(dāng)SS進(jìn)入B區(qū)域時(shí)通信開(kāi)始。通信開(kāi)始后,SCK打出通信時(shí)鐘信號(hào),在C時(shí)間段,MOSI打出1字節(jié)的高低電平信號(hào),為'0b01000110',查看ASCII碼表得知為字符'F',完成該字節(jié)的傳輸花了2us時(shí)間。然后停頓1us后,再在D時(shí)間段打出‘0b01100000‘,對(duì)應(yīng)字符'a'。同理,在E時(shí)間段發(fā)送'b'字符。SS信號(hào)到G時(shí)間段開(kāi)始轉(zhuǎn)為高電平,通信中止,完成了'Fab'三個(gè)字符從Master到Slave的數(shù)據(jù)傳輸。
 
上述‘F‘字符的二進(jìn)制可以從圖3清楚分析得到。由于這里2us完成了一個(gè)字節(jié)的傳輸,因此此例中,理論傳輸速度可以容易計(jì)算得到:8b*1s/2us=4,000,000bps,即4Mbps。

圖3. 'F'字符的傳輸時(shí)序。
 
上圖中,SCLK信號(hào)線由低電平轉(zhuǎn)高電平(高電平采樣,上升沿有效)時(shí)進(jìn)行信號(hào)1個(gè)bit的采樣,對(duì)應(yīng)MOSI線為低電平時(shí)為0,高電平時(shí)為1。這里SCLK在上升沿時(shí)對(duì)MOSI或MISO數(shù)據(jù)線進(jìn)行采樣,這是SPI通信中的Mode3。在SPI通信中,一共有4種模式,可以通過(guò)設(shè)置CPOL和CPHA這兩個(gè)寄存器位來(lái)實(shí)現(xiàn)這4種通信模式。CPOL是Clock POLarity,CPHA是Clock PHAse。這兩個(gè)設(shè)置位最早由Freescale公司設(shè)定,后被廣泛使用。4種模式的設(shè)置如下:

(1)Mode 0:時(shí)鐘正常為低電平(CPOL = 0),數(shù)據(jù)在低電平轉(zhuǎn)為高電平時(shí)采樣,即上升沿采樣(CPHA = 0)。
(2)Mode 1:時(shí)鐘正常為低電平(CPOL = 0),數(shù)據(jù)在高電平轉(zhuǎn)為低電平時(shí)采樣,即下降沿采樣(CPHA = 1)
(3)Mode 2:時(shí)鐘正常為高電平(CPOL = 1),數(shù)據(jù)在高電平轉(zhuǎn)為低電平時(shí)采樣,即下降沿采樣(CPHA = 0)。
(4)Mode 3:時(shí)鐘正常為高電平(CPOL = 1),數(shù)據(jù)在低電平轉(zhuǎn)為高電平時(shí)采樣,即上升沿采樣(CPHA = 1)。

Arduino的默認(rèn)模式為Mode 0。這個(gè)Mode x中x的值就是CPOL和CPHA二進(jìn)制組合的值,所以很好記。

這4種模式的圖示見(jiàn)圖4至圖7。


圖4. Mode 0 (CPOL = 0CPHA = 0)
 

圖5. Mode 1 (CPOL = 0CPHA = 1)
 

圖6. Mode 2 (CPOL = 1CPHA = 0)
 

圖7. Mode 2 (CPOL = 1CPHA = 1)
 

三、NDS的SPI分析

可以這么說(shuō),NDS掌機(jī)與所有外設(shè)的通信連接都采用SPI接口。例如電源管理模塊、觸摸屏、麥克風(fēng)、以及Slot1卡帶都使用SPI通信。其中,前三者通過(guò)SPI控制寄存器REG_SPICNT和SPI數(shù)據(jù)寄存器REG_SPIDATA管理,REG_SPICNTREG_SPIDATA分別映射于內(nèi)存地址:0x040001C00x040001C2。由于本篇主要目的是為了通過(guò)slot 1接口擴(kuò)展,因此對(duì)這三者的控制不做介紹。
 
Slot 1卡帶的SPI通信由AUXSPICNT寄存器和AUXSPIDATA寄存器控制,這兩者分為控制寄存器和數(shù)據(jù)寄存器,分別映射在0x040001A00x040001A2的內(nèi)存地址。均為16位寄存器。

3.1 Slot 1卡帶方問(wèn)權(quán)歸屬設(shè)置

由于這兩個(gè)寄存器NDS的ARM7和ARM9兩個(gè)CPU均可訪問(wèn),因此在使用前需要先設(shè)置訪問(wèn)權(quán)歸屬,這可以通過(guò)設(shè)置位于0x04000204REG_EXMEMCNT寄存器第11位實(shí)現(xiàn),置0為ARM9訪問(wèn),置1為ARM7訪問(wèn)。詳見(jiàn)圖8。


圖8. REG_EXMEMCNT寄存器。

一般我會(huì)讓ARM9主CPU來(lái)控制SPI的通信,因此可以這么設(shè)置:

REG_EXMEMCNT &= (~(1<<11));
 
3.2 SPI總線初始化

設(shè)置完Slot 1的訪問(wèn)權(quán)歸屬后,接下來(lái)需要對(duì)SPI進(jìn)行初始化。這里的初始化很簡(jiǎn)單,因?yàn)樵谡嬲褂肧PI通信前,我們將關(guān)閉SPI通信,這通過(guò)使AUXSPICNT寄存器最高位(第15位)置0實(shí)現(xiàn):


圖9. AUXSPICNT寄存器各位說(shuō)明。

接下來(lái),是配置SPI各個(gè)參數(shù)。從圖9可知,如果需要1MHz的通信速率,可以置AUXSPICNT第0和1位為0b01實(shí)現(xiàn)。然后再設(shè)置SPI通信的IRQ使能,即置第14位為1。另外因?yàn)镾lot 1接口有兩種工作模式:ROM模式和SPI通信模式,我們要使用的當(dāng)然是SPI通信模式,因此還需要置第13位為1。我打算使用SPI通信的方式為使用時(shí)打開(kāi),不使用時(shí)就關(guān)閉,默認(rèn)為關(guān)閉狀態(tài),因此我將不直接設(shè)置各參數(shù)到AUXSPICNT寄存器上,而是先賦給一個(gè)16位變量u16 config,在需要通信時(shí)再將這個(gè)config值直接賦給AUXSPICNT寄存器,使之立即生效可用。于是有:

config = 1 | (1<<14) | (1<<13);

3.3 SPI通信
當(dāng)該NDS通過(guò)該SPI與外界進(jìn)行實(shí)際通信時(shí),操作如下:
(1)發(fā)送數(shù)據(jù):檢查AUXSPICNT寄存器第7位,查看是否SPI總線忙,如果忙,則等待直至不忙(第7位為0)。然后將config值賦給AUXSPICNT寄存器并同時(shí)置該寄存器第6位為1,意即SS信號(hào)打低電平準(zhǔn)備SPI通信。實(shí)現(xiàn)如下:
AUXSPICNT = config | (1<<6);
 
注意一點(diǎn):非官方文檔GBA/NDS Technical Info中有一行說(shuō)明:

The "Hold" flag should be cleared BEFORE transferring the LAST data unit, the chipselect will be then automatically cleared after the transfer, the program should issue a WaitByLoop(12) (on NDS7, or longer on NDS9) manually AFTER the LAST transfer.

這說(shuō)明傳完數(shù)據(jù)后,SS線(第6位)會(huì)自動(dòng)清0,即關(guān)閉通信。另外通信結(jié)束后需要等待一段時(shí)間,可能是因?yàn)閿?shù)據(jù)在物理信道上傳輸和存儲(chǔ)到接收方寄存器的過(guò)程需要時(shí)間。

然后便可以將8位單字節(jié)數(shù)據(jù)賦給AUXSPIDATA寄存器,硬件會(huì)自動(dòng)將數(shù)據(jù)發(fā)送出去。注意SPI通信一般單次傳輸8 bit,AUXSPIDATA寄存器也是如此,見(jiàn)圖10。


圖10. AUXSPIDATA寄存器說(shuō)明。
注意:上述文獻(xiàn)中也有一行說(shuō)明:

During transfer, the Busy flag in AUXSPICNT is set, and the written DATA value is transferred to the device (via output line), simultaneously data is received (via input line). Upon transfer completion, the Busy flag goes off, and the received value can be then read from AUXSPIDATA, if desired.

這行英文說(shuō)明數(shù)據(jù)一傳出去,如果外面?zhèn)饔羞^(guò)來(lái)的數(shù)據(jù),則同時(shí)也就通過(guò)MISO接收到了。而且數(shù)據(jù)傳輸一結(jié)束,busy位(即AUXSPICNT寄存器第7位)將自動(dòng)清0,于是接收的數(shù)據(jù)就能從AUXSPIDATA寄存器取到了。

(2)接收數(shù)據(jù):上面的那段英文和我的注釋已經(jīng)說(shuō)得很清楚了:只要busy位為0,即可從AUXSPIDATA寄存器獲取接收數(shù)據(jù)。這是因?yàn)闃?biāo)準(zhǔn)SPI通信是全雙工的,一方在一個(gè)時(shí)鐘節(jié)拍內(nèi)發(fā)出1 bit的同時(shí),也接收另一方發(fā)過(guò)來(lái)的1 bit。這在維基百科詞條:Serial Peripheral Interface Bus里有段原文說(shuō)明:
 

圖11. Wikipedia中關(guān)于SPI通信的描述。

三、Arduino的SPI分析
由于Arduino的開(kāi)發(fā)包的高度封裝,因此Arduino端的SPI通信編程相對(duì)來(lái)講容易的多。這可以采用兩種方式實(shí)現(xiàn):

(1)完全通過(guò)底層操作ATmega CPU的寄存器實(shí)現(xiàn)。
(2)通過(guò)SPI庫(kù)來(lái)實(shí)現(xiàn)主要功能,適當(dāng)添加部分寄存器操作。

DS brut采用第(1)種方式,代碼不便閱讀和理解,編程調(diào)試也較難。我將采用第二種方式實(shí)現(xiàn)。

由于Arduino的SPI庫(kù)只支持將Arduino作為Master設(shè)備去連接外部的Slave設(shè)備,因此需要稍做寄器存器設(shè)置:
  // turn on SPI in slave mode 
 SPCR |= _BV(SPE);
另外,為提高執(zhí)行效率,可采用中斷方式處理SPI通信:
  // turn on interrupts 
 SPCR |= _BV(SPIE);
以上兩部分代碼放入setup()函數(shù)中便可。
如果Arduino UNO端采用10號(hào)數(shù)字引腳作為SS線,則可以將10號(hào)線連接到2號(hào)線,這樣當(dāng)SS線電平被拉低或拉高時(shí)便會(huì)觸發(fā)2號(hào)線的中斷。由于2號(hào)線的中斷號(hào)為0,因此我們可在setup()函數(shù)最后加入以下代碼來(lái)捕獲0號(hào)中斷:
  // interrupt for SS falling edge 
 attachInterrupt (0, ss_falling, FALLING);
以下是完整的Arduino 做為SPI Slave的代碼的Demo,來(lái)源:SPI - Serial Peripheral Interface - for Arduino。
 
 
// Written by Nick Gammon
// April 2011

#include "pins_arduino.h"

// what to do with incoming data
byte command = 0;

// start of transaction, no command yet
void ss_falling ()
{
  command = 0;
}  // end of interrupt service routine (ISR) ss_falling

void setup (void)
{

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  // interrupt for SS falling edge
  attachInterrupt (0, ss_falling, FALLING);
  
}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte c = SPDR;
 
  switch (command)
  {
  // no command? then this is the command
  case 0:
    command = c;
    SPDR = 0;
    break;
    
  // add to incoming byte, return result
  case 'a':
    SPDR = c + 15;  // add 15
    break;
    
  // subtract from incoming byte, return result
  case 's':
    SPDR = c - 8;  // subtract 8
    break;

  } // end of switch

}  // end of interrupt service routine (ISR) SPI_STC_vect


void loop (void)
{
// all done with interrupts
}  // end of loop
 
圖12是上述代碼中斷執(zhí)行的時(shí)序圖。


圖12. 中斷服務(wù)過(guò)程調(diào)用時(shí)序圖。
 
此篇結(jié)束,敬請(qǐng)期待下篇。
關(guān)閉窗口

相關(guān)文章