1. 程式人生 > 實用技巧 >SPI協議,MCP2515裸機驅動詳解

SPI協議,MCP2515裸機驅動詳解

SPI概述

Serial Peripheral interface 通用序列外圍裝置介面

是Motorola首先在其MC68HCXX系列處理器上定義的。SPI介面主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字訊號處理器和數字訊號解碼器之間。

SPI,是一種高速的,全雙工,同步的通訊匯流排,並且在晶片的管腳上只佔用四根線,節約了晶片的管腳,同時為PCB的佈局上節省空間。

SPI特點

採用主-從模式(Master-Slave) 的控制方式

SPI 規定了兩個 SPI 裝置之間通訊必須由主裝置 (Master) 來控制次裝置 (Slave). 一個 Master 裝置可以通過提供 Clock 以及對 Slave 裝置進行片選 (Slave Select) 來控制多個 Slave 裝置, SPI 協議還規定 Slave 裝置的 Clock 由 Master 裝置通過 SCK 管腳提供給 Slave 裝置, Slave 裝置本身不能產生或控制 Clock, 沒有 Clock 則 Slave 裝置不能正常工作。

而這裡的SPI中的時鐘和相位,指的就是SCLk時鐘的特性,即保證主從裝置兩者的時鐘的特性一致了,以保證兩者可以正常實現SPI通訊。

採用同步方式(Synchronous)傳輸資料

Master 裝置會根據將要交換的資料來產生相應的時鐘脈衝(Clock Pulse), 時鐘脈衝組成了時鐘訊號(Clock Signal) , 時鐘訊號通過時鐘極性 (CPOL) 和 時鐘相位 (CPHA) 控制著兩個 SPI 裝置間何時資料交換以及何時對接收到的資料進行取樣, 來保證資料在兩個裝置之間是同步傳輸的.

工作機制

概述

首先看下SPI Data Transfer模組圖。

上圖只是對 SPI 裝置間通訊的一個簡單的描述, 下面詳細解釋一下圖中所示的幾個元件(Module):

SSPBUF

Synchronous Serial Port Buffer, 泛指 SPI 裝置裡面的內部緩衝區, 一般在物理上是以 FIFO 的形式, 儲存傳輸過程中的臨時資料;

我們知道, 在每個時鐘週期內, Master 與 Slave 之間交換的資料其實都是 SPI 內部移位暫存器從 SSPBUF 裡面拷貝的. 我們可以通過往 SSPBUF 對應的暫存器 (Tx-Data / Rx-Data register) 裡讀寫資料, 間接地操控 SPI 裝置內部的 SSPBUF。

例如, 在傳送資料之前, 我們應該先往 Master 的 Tx-Data 暫存器寫入將要傳送出去的資料, 這些資料會被 Master-SSPSR 移位暫存器根據 Bus-Width 自動移入 Master-SSPBUF 裡, 然後這些資料又會被 Master-SSPSR 根據 Channel-Width 從 Master-SSPBUF 中移出, 通過 Master-SDO 管腳傳給 Slave-SDI 管腳, Slave-SSPSR 則把從 Slave-SDI 接收到的資料移入 Slave-SSPBUF 裡. 與此同時, Slave-SSPBUF 裡面的資料根據每次接收資料的大小(Channel-Width), 通過 Slave-SDO 發往 Master-SDI, Master-SSPSR 再把從 Master-SDI 接收的資料移入 Master-SSPBUF.在單次資料傳輸完成之後, 使用者程式可以通過從 Master 裝置的 Rx-Data 暫存器讀取 Master 裝置資料交換得到的資料。

SSPSR

Synchronous Serial Port Register, 泛指 SPI 裝置裡面的移位暫存器(Shift Regitser), 它的作用是根據設定好的資料位寬(bit-width) 把資料移入或者移出 SSPBUF;

SSPSR 是 SPI 裝置內部的移位暫存器(Shift Register). 它的主要作用是根據 SPI 時鐘訊號狀態, 往 SSPBUF 裡移入或者移出資料, 每次移動的資料大小由 Bus-Width 以及 Channel-Width 所決定。
Bus-Width 的作用是指定地址匯流排到 Master 裝置之間資料傳輸的單位.
例如, 我們想要往 Master 裝置裡面的 SSPBUF 寫入 16 Byte 大小的資料: 首先, 給 Master 裝置的配置暫存器設定 Bus-Width 為 Byte; 然後往 Master 裝置的 Tx-Data 移位暫存器在地址匯流排的入口寫入資料, 每次寫入 1 Byte 大小的資料(使用 writeb 函式); 寫完 1 Byte 資料之後, Master 裝置裡面的 Tx-Data 移位暫存器會自動把從地址匯流排傳來的1 Byte 資料移入 SSPBUF 裡; 上述動作一共需要重複執行 16 次。

Channel-Width 的作用是指定 Master 裝置與 Slave 裝置之間資料傳輸的單位. 與 Bus-Width 相似, Master 裝置內部的移位暫存器會依據 Channel-Width 自動地把資料從 Master-SSPBUF 裡通過 Master-SDO 管腳搬運到 Slave 裝置裡的 Slave-SDI 引腳, Slave-SSPSR 再把每次接收的資料移入 Slave-SSPBUF裡.通常情況下, Bus-Width 總是會大於或等於 Channel-Width, 這樣能保證不會出現因 Master 與 Slave 之間資料交換的頻率比地址匯流排與 Master 之間的資料交換頻率要快, 導致 SSPBUF 裡面存放的資料為無效資料這樣的情況

Controller

泛指 SPI 裝置裡面的控制暫存器, 可以通過配置它們來設定 SPI 匯流排的傳輸模式。
通常情況下, 我們只需要對上圖所描述的四個管腳(pin) 進行程式設計即可控制整個 SPI 裝置之間的資料通訊。

Master 裝置裡面的 Controller 主要通過時鐘訊號(Clock Signal)以及片選訊號(Slave Select Signal)來控制 Slave 裝置. Slave 裝置會一直等待, 直到接收到 Master 裝置發過來的片選訊號, 然後根據時鐘訊號來工作。

Master 裝置的片選操作必須由程式所實現. 例如: 由程式把 SS/CS 管腳的時鐘訊號拉低電平, 完成 SPI 裝置資料通訊的前期工作; 當程式想讓 SPI 裝置結束資料通訊時, 再把 SS/CS 管腳上的時鐘訊號拉高電平.

SCK

Serial Clock, 主要的作用是 Master 裝置往 Slave 裝置傳輸時鐘訊號, 控制資料交換的時機以及速率;

SS/CS

Slave Select/Chip Select, 用於 Master 裝置片選 Slave 裝置, 使被選中的 Slave 裝置能夠被 Master 裝置所訪問。

SDO/MOSI

Serial Data Output/Master Out Slave In, 在 Master 上面也被稱為 Tx-Channel, 作為資料的出口, 主要用於 SPI 裝置傳送資料。

SDI/MISO

Serial Data Input/Master In Slave Out, 在 Master 上面也被稱為 Rx-Channel, 作為資料的入口, 主要用於SPI 裝置接收資料。

SPI 裝置在進行通訊的過程中, Master 裝置和 Slave 裝置之間會產生一個數據鏈路迴環(Data Loop), 就像上圖所畫的那樣, 通過 SDO 和 SDI 管腳, SSPSR 控制資料移入移出 SSPBUF, Controller 確定 SPI 匯流排的通訊模式, SCK 傳輸時鐘訊號。

極性和相位

要想搞清楚SPI的資料傳輸,首先要搞清楚相位和極性的概念,即SPI的極性Polarity和相位Phase。

最常見的寫法是CPOL和CPHA,不過也有一些其他寫法,簡單總結如下:

  • (1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (時鐘)極性
  • (2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (時鐘)相位
  • (3) SCK=SCLK=SPI的時鐘
  • (4) Edge=邊沿,即時鐘電平變化的時刻,即上升沿(rising edge)或者下降沿(falling edge)

對於一個時鐘週期內,有兩個edge,分別稱為:

  • (1)Leading edge=前一個邊沿=第一個邊沿,對於開始電壓是1,
    那麼就是1變成0的時候,對於開始電壓是0,那麼就是0變成1的時候;

  • (2)Trailing edge=後一個邊沿=第二個邊沿,對於開始電壓是1,
    那麼就是0變成1的時候(即在第一次1變成0之後,才可能有後面的0變成1),
    對於開始電壓是0,那麼就是1變成0的時候;

本博文采用如下用法:

  1. 極性=CPOL
  2. 相位=CPHA
  3. SCLK=時鐘
  4. 第一個邊沿和第二個邊沿

CPOL和CPHA,分別都可以是0或時1,對應的四種組合就是:

模式 極性 相位
Mode 1 CPOL=0,CPHA=0
Mode 2 CPOL=0,CPHA=1
Mode 3 CPOL=1,CPHA=0
Mode 4 CPOL=1,CPHA=1

CPOL=0,CPHA=0

脈衝傳輸前和完成後都保持在低電平狀態,所以 CPOL=0,即低電平是空閒時的電平。在第一個邊沿(上升沿)取樣資料,第二個邊沿(下降沿)輸出資料,對應著CPHA=0。

CPOL=0,CPHA=1

脈衝傳輸前和完成後都保持在低電平狀態,所以 CPOL=0,即低電平是空閒時的電平。在第二個邊沿(下降沿)取樣資料,第一個邊沿(上升沿)輸出資料,對應著CPHA=1。

CPOL=1,CPHA=0

脈衝傳輸前和完成後都保持在高電平狀態,所以 CPOL=1,即高電平是空閒時的電平。在第一個邊沿(下降沿)取樣資料,第二個邊沿(上升沿)輸出資料,對應著CPHA=0。

CPOL=1,CPHA=1

脈衝傳輸前和完成後都保持在高電平狀態,所以 CPOL=1,即高電平是空閒時的電平。在第二個邊沿(上升沿)取樣資料,第一個邊沿(下降沿)輸出資料,對應著CPHA=1。

軟體中如何設定SPI的極性和相位

SPI分主裝置和從裝置,兩者通過SPI協議通訊。
而設定SPI的模式,是從裝置的模式,決定了主裝置的模式。
所以要先去搞懂從裝置的SPI是何種模式,然後再將主裝置的SPI的模式,設定和從裝置相同的模式,即可正常通訊。

對於從裝置的SPI是什麼模式,有兩種:

  • (1)固定的,有SPI從裝置硬體決定的
    SPI從裝置,具體是什麼模式,相關的datasheet中會有描述,需要自己去datasheet中找到相關的描述,即:
    關於SPI從裝置,在空閒的時候,是高電平還是低電平,即決定了CPOL是0還是1;
    然後再找到關於裝置是在上升沿還是下降沿去取樣資料,這樣就是,在定了CPOL的值的前提下,對應著可以推算出CPHA是0還是1了。

  • (2)可配置的,由軟體自己設定
    從裝置也是一個SPI控制器,4種模式都支援,此時只要自己設定為某種模式即可。
    然後知道了從裝置的模式後,再去將SPI主裝置的模式,設定為和從裝置模式一樣即可。
    對於如何配置SPI的CPOL和CPHA的話,不多細說,多數都是直接去寫對應的SPI控制器中對應暫存器中的CPOL和CPHA那兩位,寫0或寫1即可。

資料交換(Data Exchanges)

SPI只有主模式和從模式之分,沒有讀和寫的說法,因為實質上每次SPI是主從裝置在交換資料。也就是說,你發一個數據必然會收到一個數據;你要收一個數據必須也要先發一個數據。

SPI 裝置間的資料傳輸之所以又被稱為資料交換, 是因為 SPI 協議規定一個 SPI 裝置不能在資料通訊過程中僅僅只充當一個 "傳送者(Transmitter)" 或者 "接收者(Receiver)"。 在每個 Clock 週期內, SPI 裝置都會發送並接收一個 bit 大小的資料, 相當於該裝置有一個 bit 大小的資料被交換了。

一個 Slave 裝置要想能夠接收到 Master 發過來的控制訊號, 必須在此之前能夠被 Master 裝置進行訪問 (Access)。 所以, Master 裝置必須首先通過 SS/CS pin 對 Slave 裝置進行片選, 把想要訪問的 Slave 裝置選上。 在資料傳輸的過程中, 每次接收到的資料必須在下一次資料傳輸之前被取樣. 如果之前接收到的資料沒有被讀取, 那麼這些已經接收完成的資料將有可能會被丟棄, 導致 SPI 物理模組最終失效。

因此, 在程式中一般都會在 SPI 傳輸完資料後, 去讀取 SPI 裝置裡的資料, 即使這些資料(Dummy Data)在我們的程式裡是無用的。

SPI舉例

下面舉一個例子幫助大家理解。

SPI是一個環形匯流排結構,由ss(cs)、sck、sdi、sdo構成,其時序其實很簡單,主要是在sck的控制下,兩個雙向移位暫存器進行資料交換。

假設下面的8位暫存器裝的是待發送的資料10101010,上升沿傳送、下降沿接收、高位先發送。
那麼第一個上升沿來的時候 資料將會是sdo=1;暫存器=0101010x。下降沿到來的時候,sdi上的電平將所存到暫存器中去,那麼這時暫存器=0101010sdi,這樣在 8個時鐘脈衝以後,兩個暫存器的內容互相交換一次。這樣就完成裡一個spi時序。

舉例:
假設主機和從機初始化就緒:並且主機的sbuff=0xaa,從機的sbuff=0x55,下面將分步對spi的8個時鐘週期的資料情況演示一遍:假設上升沿傳送資料

這樣就完成了兩個暫存器8位的交換,上面的上表示上升沿、下表示下降沿,sdi、sdo相對於主機而言的。

下一步就是把 上面的過程轉為動畫


請仔細比較下交換後的bit順序。

瞭解了SPI協議說明之後,下面我們基於Cortex-A9架構的exynos-4412,講解SPI控制器的使用。

Cortex-A9 SPI控制器

硬體設計

本例是基於FS4412開發板,SPI控制器外接了MCP2515,MCP2515與exynos-4412的硬體連線圖如下圖所示。



管腳連線說明

由上圖可知SPI個引腳與SOC的pin連線關係:

  • CS <---------> BUF_BK_LED <---------> GPC1_2

  • SO <---------> BUF_I2C_SDA6 <---------> GPC1_3

  • SI <---------> BUF_I2C_SCL6 <---------> GPC1_4

  • SCK <---------> BUF_GPC1_1 <---------> GPC1_1

  • INT <-------------------------------------------> BUF_GPX0_0

  • MCP2515晶片連線在4412晶片的SPI2上。

  • 中斷連線在GPX0_0上;
    CS、SO、SI、SCK複用了GPIO引腳GPC1的引腳。

MCP2515輸出連線SN65HVD230 CAN匯流排收發器,SN65HVD230是德州儀器公司生產的3.3V CAN收發器。為了節省功耗,縮小電路體積,MCP2515 CAN匯流排控制器的邏輯電平採用LVTTL,SN65HVD230就是與其配套的收發器。

Cortex-A9 SPI控制器

exynos4412 scp中的序列外設介面(SPI)通過各種外設來傳輸序列資料。SPI包括兩個8、16和32位移位暫存器,用於傳輸和接收資料。在SPI傳輸過程中,它同時傳輸(序列移出)和接收(序列移位)資料。

特性

  • 全雙工
  • 用於Tx/Rx的8/16/32位移位暫存器
  • 支援8位/16位/32位匯流排介面
  • 支援摩托羅拉SPI協議和National Semiconductor Microwire
  • 兩個獨立的32位寬的傳送和接收FIFO:埠0的深度為64,埠1和2中的深度為16
  • 主模式和從模式
  • 接收而不傳送操作
  • 傳送/接收最高頻率為50 MHz

SPI的操作

SPI在Exynos 4412 SCP和外部裝置之間傳輸1位序列資料。
Exynos 4412 SCP中的SPI支援CPU或DMA分別同時傳送或接收FIFO和雙向傳輸資料。
SPI有兩個通道,即Tx通道和Rx通道。Tx通道有來自Tx的路徑

FIFO到外部裝置。Rx通道有從外部裝置到Rx FIFO的路徑。

CPU(或DMA)必須將資料寫入暫存器SPI_TX_DATA,才能將資料寫入FIFO。暫存器上的資料會自動移動到Tx FIFO。要從Rx FIFO讀取資料,CPU(或DMA)必須訪問暫存器SPI_RX_DATA,資料會自動傳送到SPI_RX_DATA暫存器。

CMU暫存器可以控制SPI的工作頻率。

操作模式

SPI有兩種模式,即主模式和從模式。
在主模式下,生成SPICLK並將其傳輸到外部裝置。XspiCS#是選擇從機的訊號,它指示在設定XspiCS時資料有效低水平。在傳送或接收資料包之前,必須將XspiCS設定為低。

FIFO存取

SPI支援對fifo的CPU訪問和DMA訪問。
對fifo的CPU訪問和DMA訪問的資料大小從8位、16位或32位資料中選擇。當它選擇8位資料大小時,有效位是0到7位元。使用者可以定義觸發閾值來引發CPU中斷。埠0中每個FIFO的觸發電平由從0到252位元組的步進為4個位元組,埠1中每個FIFO的位元組從0到63位元組按1個位元組的步長設定。
SPI_MODE_CFG暫存器的TxDMAOn或RxDMAOn位必須設定為使用DMA訪問。DMA訪問支援只有單次傳輸和4突發傳輸。在Tx FIFO中,DMA請求訊號是高的,直到Tx FIFO滿為止。在Rx FIFO中,如果FIFO不為空,DMA請求訊號高。

片選控制

晶片選擇XspiCS是啟用的低訊號。換句話說,當XspiCS輸入為0時,選擇晶片。
您可以自動或手動控制XspiCS。不需要改變。
手動模式
當您使用手動控制模式時,您應清除AUTO_N_MANUAL(預設值為0)。NSSOUT位控制XspiCS級別。
自動模式
使用自動控制模式時,必須將AUTO_N_MANUAL設定為1。XspiCS在資料包和自動打包。NCS_TIME_COUNT 控制XspiCS的非啟用期。NSSOUT在此時不可用。

SPI暫存器說明

配置暫存器CH_CFGn

【bit:5】 軟體復位的時候必須先將此位設定為1,給一個延時後再設定為0;
【bit:4】 設定SPI埠是主機還是從機,0:主機,1:從機;
【bit:2-3】設定相位和極性;
【bit:1】接收資料的通道使能位;
【bit:0】傳送資料的通道使能位。

資料寬度暫存器MODE_CFGn

設定匯流排資料寬度,一般設定為1個位元組。

片選暫存器CS_REGn

只有NSSOUT 拉低,才會進行資料的傳輸

移位暫存器狀態暫存器SPI_STATUSn

傳送操作開始,如果移位暫存器空了,該值置1,通過該值判斷資料是否傳送出去。

傳送緩衝暫存器SPI_TX_DATA

接收緩衝暫存器SPI_RX_DATA

SPI初始化流程

  1. 設定GPIO引腳為SPI模式;
  2. 設定clock;
  3. 軟體復位;
  4. 設定CPOL CPHA 為 00 模式,並設定為主機模式;
  5. 設定資料位;
  6. 片選。

1. 設定GPIO引腳為SPI模式

因為CS、SO、SI、SCK複用了GPIO引腳GPC1的引腳。首先需要設定GPIO引腳為SPI模式。

如上所示,該暫存器設定方式如下:

GPC1.CON = (GPC1.CON & ~0xffff0) | 0x55550;//設定IO引腳為SPI模式

Step 1 設定CPOL CPHA 為 00 模式

SPI2.CH_CFG&=~((0x1<< 4)|(0x1<<3)|(0x1<< 2)|0x3);

//master mode, CPOL = 0, CPHA = 0 (Format A)

2 設定clock

時鐘的設定需要依賴鎖相環(PLL)時鐘產生器。
從30.2.1節可知,時鐘配置需要參考CMU這一章。

由上圖可知SPI 的clock源是SCLK_SPI。

搜尋SCLK_SPI

從第七章搜所有的SPI
繼續搜尋

由此可知SPI0~2的之中受CLKMPLL_USER_T 控制,繼續搜尋。
而此時鐘位於暫存器CLK_SRC_PERIL1的bit[27:24]。

繼續搜尋SPI2。
搜尋到CLK_SRC_MASK_PERIL1、CLK_DIV_PERIL2。
可知暫存器CLK_SRC_MASK_PERIL1預設是開啟的,所以不用設定。
CLK_DIV_PERIL2用來設定SPI2分頻的,於是暫存器設定如下:

CLK_SRC_PERIL1 = (CLK_SRC_PERIL1 & ~(0xF<<24)) | 6<<24;
// 0x6: 0110 = SCLKMPLL_USER_T 800Mhz
CLK_DIV_PERIL2 = 19 <<8 | 3;//SPI_CLK = 800/(19+1)/(3+1)

3. 軟體復位

void soft_reset(void)
{	SPI2.CH_CFG |= 0x1 << 5;
	delay(1);                     //延時
	SPI2.CH_CFG &= ~(0x1 << 5);
}

4. 設定CPOL CPHA 為 00 模式,並設定為主機模式

SPI2.CH_CFG &= ~( (0x1 << 4) | (0x1 << 3) | (0x1 << 2) | 0x3);

5. 設定資料位MODE_CFG

SPI2.MODE_CFG &= ~((0x3 << 17) | (0x3 << 29));
   //BUS_WIDTH=8bit,CH_WIDTH=8bit 

6. 片選

SPI2.CS_REG &= ~(0x1 << 1);        //選擇手動選擇晶片

初始化搞定後,下面我們來看如何利用SPI收發資料。

收發資料流程

收發資料流程如下:

  1. spi復位
  2. 片選從機
  3. 收發資料
  4. 取消片選

【注意】一下程式碼為設定每次只讀寫1個byte資料,每次讀寫1個byte資料都要按照上述步驟操作。

1 spi復位

void soft_reset(void)
{	SPI2.CH_CFG |= 0x1 << 5;
	 delay(1);                     //延時
	 SPI2.CH_CFG &= ~(0x1 << 5);
}

2 片選從機

void slave_enable(void)
{
	SPI2.CS_REG &= ~0x1; //enable salve
	delay(3);
}

3.1 傳送資料

void send_byte(unsigned char data)
{
	SPI2.CH_CFG |= 0x1; // enable Tx Channel
	delay(1);
	SPI2.SPI_TX_DATA = data;
	while( !(SPI2.SPI_STATUS & (0x1 << 25)) );
	SPI2.CH_CFG &= ~0x1; // disable Tx Channel
}

3.2 接收資料

unsigned char recv_byte()
{
	unsigned char data;
	SPI2.CH_CFG |= 0x1 << 1; // enable Rx Channel
	delay(1);
	data = SPI2.SPI_RX_DATA;
	delay(1);
	SPI2.CH_CFG &= ~(0x1 << 1); //disable Rx Channel
	return  data;
}

取消片選

void slave_disable(void)
{

	SPI2.CS_REG |= 0x1; //disable salve
	delay(1);
}

OK,到底為止,如何通過SPI收發資料,我們已經可以實現了,但是SPI往往外部回接各種各樣的從裝置。下面我們來看SPI如何操作從裝置。

上面我們說了,fs4412開發板的SPI2外設外接了MCP2515,現在我們來分析一下MCP2515。

MCP2515

MCP2515詳細資料,大家自行搜尋 MCP2515 datasheet 《帶有 SPI 介面的獨立 CAN 控制器》。

簡介

MCP2515是一種獨立的CAN匯流排通訊控制器,是Microchip公司首批獨立CAN解決方案的升級器件,其傳輸能力較Microchip公司原有CAN控制器(MCP2510)高兩倍,高通訊速率可達到1Mbps。MCP2515能夠接收和傳送標準資料幀和擴充套件資料幀以及遠端幀,通過兩個接收遮蔽暫存器和六個接收過濾暫存器濾除無關報文,從而減輕CPU負擔。

特性

MCP2515主要功能引數及電氣特性如下:

  • (1)支援CAN技術規範2.0A/B, 高傳輸速率達到1Mbps;
  • (2)支援標準資料幀、擴充套件資料幀和遠端幀,每幀資料域長度可為0~8個位元組;
  • (3)內含兩個的接收緩衝器和三個傳送緩衝器,並且可程式設計設定優先順序;
  • (4)內含六個29位(bit)的接收過濾暫存器和兩個29位(bit)的接收遮蔽暫存器;
  • (5)高速SPI介面,支援SPI 0,0和1,1模式;
  • (6)一次性模式可確保報文被一次性傳輸;
  • (7)具有可程式設計時鐘脈衝輸出引腳,可作為其他晶片時鐘訊號源;
  • (8) 幀起始(SOF)訊號輸出功能可被用於在確定的系統中(如時間觸發CAN-TTCAN)執行時隙功能,或在CAN匯流排診斷中決定早期匯流排出級;
  • (9) 採用低功耗CMOS技術,工作電壓:2.7V~5.5V, 工作電流:5mA(待機狀態1μA);
  • (10)工作溫度範圍:(I)-40℃到+85℃,(E)-40℃到+125℃。

結構框圖

CAN模組

MCP2515 是一款獨立 CAN 控制器, 可簡化需要與 CAN匯流排連線的應用。如上圖 簡要顯示了 MCP2515 的結構框圖。該器件主要由三個部分組成:

  1. CAN 模組,包括 CAN 協議引擎、驗收濾波寄存
    器、驗收遮蔽暫存器、傳送和接收緩衝器。
  2. 用於配置該器件及其執行的控制邏輯和暫存器。
  3. SPI 協議模組。

CAN 模組的功能是處理所有 CAN 總線上的報文接收和傳送。報文傳送時,首先將報文裝載到正確的報文緩衝器和控制暫存器中。通過 SPI 介面設定控制暫存器中的相應位或使用傳送使能引腳均可啟動傳送操作。通過讀取相應的暫存器可以檢查通訊狀態和錯誤。 會對在 CAN總線上檢測到的任何報文進行錯誤檢查,然後與使用者定義的濾波器進行匹配,以確定是否將報文移到兩個接收緩衝器中的一個。

SPI 協議模組

MCU通過SPI介面與該器件連線。使用標準的SPI讀/寫指令以及專門的 SPI 命令來讀 / 寫所有的暫存器。
MCP2515 設計可與許多微控制器的序列外設介面( SPI)直接相連,支援 0,0 和 1,1 執行模式。外部資料和命令通過 SI 引腳傳送到器件中,且資料在 SCK 時鐘訊號的上升沿傳送進去。 MCP2515 在 SCK 的下降沿通過 SO引腳傳送出去。在進行任何操作時, CS 引腳都必須保持為低電平。

與MCP2515通訊

SPI指令集

我們要想操作MCP2515,只能用過SPI匯流排向MCP2515傳送一些資料,根據規定,傳送不同的資料就代表不同的操作,於是就形成了對應的指令集。
如上圖所示,比如我們要執行復位操作,那麼我只需要通過SPI匯流排寫入11000000,即0xC0即可。

下面我們來詳細分析如何向MCP2515傳送復位命令,如何讀寫資料。

復位

因為復位只需要傳送0XC0即可,並不需要其他額外操作,所以我們只需要根據我們之前章節所講的SPI傳送資料流程操作介面。

void reset_2515()
{
	soft_reset();      //復位spi控制器
    slave_enable() ;   //片選從機
	send_byte(0xc0);   //傳送復位命令
	slave_disable() ;  //取消片選

}

讀取資料

由上圖可知,讀取資料流程如下:

  1. 片選從機
  2. 通過SPI傳送指令0x03
  3. 傳送地址
  4. 讀取資料
  5. 取消片選
unsigned char read_byte_2515(unsigned char Addr)
{
	unsigned char ret;
    slave_enable();
    send_byte(0x03);
    send_byte(Addr);
    ret = recv_byte();
    slave_disable();
    return(ret);
}

傳送資料

由上圖可知,傳送資料流程如下:

  1. 片選從機
  2. 通過SPI傳送指令0x02
  3. 傳送地址
  4. 傳送資料
  5. 取消片選
void write_byte_2515(unsigned char addr,unsigned char data)
{
    slave_enable();
    send_byte(0x02);
    send_byte(addr);
    send_byte(data);
    slave_disable();
}

讀RX緩衝器

裝載TX緩衝器

請求傳送(RTS)指令

位修改指令

有些暫存器要修改對應的bit,必須先發送該指令,傳送遮蔽位元組,然後才可以修改遮蔽位元組中對應位為1的值,後面會有例子給出。

CAN

知道該如何和CAN通訊,下面我們來看如何初始化CAN。

CAN初始化

CAN的初始化步驟如下:

  1. MCP2515復位
  2. 設定MCP2515為配置模式
  3. 位定時配置,有配置暫存器CNF1,CNF2,CNF3控制
  4. 中斷使能
  5. 接收緩衝器配置
  6. 引腳控制暫存器和狀態暫存器

此外為方便測試,我們再加一步:
7. 迴環模式,用於測試

1. MCP2515復位

上一節已經實現。

void reset_2515()

2. 設定MCP2515為配置模式


由上圖所示,只需要向地址0X0F寫入資料0x80即可。

 write_byte_2515(0x0f, 0x80);    //CANCTRL暫存器--進入配置模式 中文DATASHEET 58頁

3. 位定時配置,有配置暫存器CNF1,CNF2,CNF3控制

CAN總線上的所有節點都必須具有相同的標稱位元率。CAN 協議採用不歸零( Non Return to Zero, NRZ)編碼方式,在資料流中不對時鐘訊號進行編碼。因此,接收時鐘訊號必須由接收節點恢復並與傳送器的時鐘同步。

由於不同節點的振盪器頻率和傳輸時間不同,接收器應具有某種能與資料傳輸邊沿同步的鎖相環( Phase Lock Loop, PLL)來同步時鐘並保持這種同步。

鑑於資料採用 NRZ 編碼,有必要進行位填充以確保至少每 6 位時間發生一次邊沿,使數字鎖相環 ( Digital Phase LockLoop, DPLL)同步。MCP2515 通過 DPLL 實現位定時。 DPLL 被配置成同輸入資料同步,併為傳送資料提供標稱定時。 DPLL 將每一 個 位 時 間 分 割 為 由 最 小 單 位 為 時 間 份 額 ( Time Quanta, TQ)所組成的多個時間段。

在位時間幀中執行的匯流排定時功能,例如與本地振盪器同步、網路傳輸延遲補償和取樣點定位等,都是由DPLL 的可程式設計位定時邏輯來規定的。

CNF1
BRP<5:0> 控制波特率預分頻比的設定。這些位根據OSC1 輸入頻率設定 TQ 的時間長度。當 BRP<5:0> =‘b000000’, TQ 最小值取 2 TOSC。通過 SJW<1:0> 選擇以 TQ 計的同步跳轉寬度。

CNF2
PRSEG<2:0> 位設定以 TQ 計的傳播段時間長度。PHSEG1<2:0>位設定以TQ計的相位緩衝段PS1的時間長度。

CNF3
如果 CNF2.BTLMODE 位為 1,則相位緩衝段 PS2 的時間長度將由 PHSEG2<2:0> 位設定,以 TQ 計。如果BTLMODE 位為 0,則 PHSEG2<2:0> 位不起作用。



MCP2515 波特率配置以16M晶振為例, SJW 段數(1+PRSEG+PRSEG1+PRSEG2)

#define CNF1_20K     0xd3        //4   20(1+4+8+7) 
#define CNF2_20K     0xfb          
#define CNF3_20K     0x46           

//可以設定的波特率 5K 10K 15K 20K 25K 40K 50K 80K 100K 125K 200K 400K 500K 667K 800K 1M
write_byte_2515(0x2A, CNF1_20K); //CNF1位定時配置寄器  
write_byte_2515(0x29, CNF2_20K); //CNF2位定時配置寄器 
write_byte_2515(0x28, CNF3_20K); //CNF3位定時配置寄器 

4. 中斷使能

MCP2515 有八個中斷源。 CANINTE 暫存器包含了使能各中斷源的中斷使能位。 CANINTF 暫存器包含了各中斷源的中斷標誌位。當發生中斷時, INT 引腳將被MCP2515 拉為低電平, 並保持低電平狀態直至 MCU 清除中斷。中斷只有在引起相應中斷的條件消失後,才會被清除。
建議在對 CANINTF 暫存器中的標誌位進行復位操作時,採用位修改命令而不要使用一般的寫操作。這是為了避免在寫命令執行過程中無意間修改了標誌位,進而導致中斷丟失。
應該注意的是, CANINTF 中的中斷標誌位是可讀寫位,因此在相關 CANINTE 中斷使能位置 1 的前提下,對上述任一位置 1 均可使 MCU 產生中斷請求。

write_byte_2515(0x2B, 0x1f);     //CANINTE中斷使能暫存器  

5. 接收緩衝器配置

MCP2515 具有兩個全接收緩衝器。每個接收緩衝器配備 有多 個 驗 收濾 波 器。除 上述 專 用 接收 緩 衝 器外,MCP2515 還具有單獨的報文整合緩衝器 ( Message Assembly Buffer, MAB) ,可作為第三個接收緩衝器。
bit5:6設定為1,其餘位暫時不用,設定為0.

 write_byte_2515(0x60, 0x60);   //RXB0CTRL接收緩衝器0 控制暫存器 

6. 引腳控制暫存器和狀態暫存器

當引腳配置為數字輸出引腳時,相應的接收緩衝器中的BFPCTRL.BxBFM位應被清零, 而BFPCTRL.BnBFE位應被置 1。在這種工作模式下,引腳的狀態由 BFPCTRL.BnBFS位控制。 BnBFS位寫入1時,將使相應的緩衝器滿中斷引腳輸出高電平,寫入 0 將使該引腳輸出低電平。當引腳處於這種模式時,該引腳的狀態只應通過位修改 SPI 命令來修改,以避免任何緩衝器滿中斷引腳出現干擾。

void bit_modify_2515(unsigned char addr, unsigned char mask, unsigned char data)
{
//    CS_SPI = 0 ;
    slave_enable() ;
    send_byte(0x05) ;
    send_byte(addr) ;
    send_byte(mask) ;
    send_byte(data) ;
    slave_disable() ;
//    CS_SPI = 1 ;
}
bit_modify_2515(0x0C, 0x0f, 0x0f); //BFPCTRL_RXnBF 引腳控制暫存器和狀態暫存器 中文DATASHEET 29 頁

7. 迴環模式,用於測試

write_byte_2515(0x0f, 0x40);   //CAN控制暫存器--迴環模式,用於測試

can緩衝區資料收發

MCP2515 採用三個傳送緩衝器。每個傳送緩衝器佔用14 位元組的 SRAM,並對映到器件儲存器中。
其中第一個位元組 TXBnCTRL 是與報文緩衝器相關的控制暫存器。該暫存器中的資訊決定了報文在何種條件下發送,並在報文傳送時指示其狀態 。

用 5 個位元組來裝載標準和擴充套件識別符號以及其他報文仲裁資訊(見暫存器 3-3 到暫存器 3-7) 。最後 8 個位元組用於裝載等待發送報文的 8 個可能的資料位元組 。

至少須將 TXBnSIDH、 TXBnSIDL 和 TXBnDLC 暫存器裝載資料。如果報文包含資料位元組,還需要對 TXBnDm暫存器進行裝載。若報文采用擴充套件識別符號,應對 TXBnEIDm 暫存器進行裝載,並將 TXBnSIDL.EXIDE 位置1。

下面我們看如何向CAN的緩衝區0傳送和接收資料。

資料傳送

can緩衝區0資料傳送流程如下:

  1. 設定為傳送最高優先順序
  2. 設定傳送緩衝器0標準識別符號高位
  3. 設定傳送緩衝器0標準識別符號低位
  4. 設定傳送緩衝器0資料長度碼8位元組
  5. 向緩衝區寫入資料,地址從0x36起
  6. 傳送請求命令0x81,傳送資料

1. 設定為傳送最高優先順序

write_byte_2515(0x30, 0x03); //設定為傳送最高優先順序

2. 設定傳送緩衝器0標準識別符號高位

write_byte_2515(0x31, 0xff); //傳送緩衝器0標準識別符號高位

3. 設定傳送緩衝器0標準識別符號低位

write_byte_2515(0x32, 0x00); //傳送緩衝器0標準識別符號低位

4. 設定傳送緩衝器0資料長度碼8位元組

write_byte_2515(0x35, 0x08);  //傳送緩衝器0資料長度碼8位元組

5. 向緩衝區寫入資料,地址從0x36起

write_byte_2515(0x36+i ,tx_buff[i]); //向txb緩衝器中寫入8個位元組

6. 傳送請求命令0x81,傳送資料

void send_req_2515()
{
 //   CS_SPI = 0; //復位
	soft_reset();      //復位spi控制器
    slave_enable() ;   //片選從機
	send_byte(0x81);   //傳送請求命令
	slave_disable() ;  //取消片選
//	CS_SPI=1;
}

Can資料的接收

從CAN緩衝區讀取資料流程如下:

  1. 讀取中斷標誌暫存器0x2c的value,判斷bit0是否為1
  2. 從接收緩衝區讀走資料,地址從0X66開始
  3. 軟復位
  4. 向中斷標誌暫存器0x2c寫入位掩碼
  5. 向中斷標誌暫存器0x2c,寫入資料0,清終端

1. 讀取中斷標誌暫存器0x2c的value,判斷bit0是否為1

當報文傳送至某一接收緩衝器時,與該接收緩衝器對應的 CANINTF.RXnIF 位將置 1。一旦緩衝器中的報文處理完畢, MCU 就必須將該位清零以接收下一條報文。該控制位提供的鎖定功能確保 MCU 尚未處理完上一條報文前, MCP2515 不會將新的報文載入接收緩衝器。

如果 CANINTE.RXnIE 位被置 1,器件會在 INT 引腳產生一箇中斷,顯示接收到報文有效。另外,如果被配置為接收緩衝器滿中斷引腳,與之相應的 RXnBF 引腳會被拉低。

MCP2515 有八個中斷源。 CANINTE 暫存器包含了使能各中斷源的中斷使能位。 CANINTF 暫存器包含了各中斷源的中斷標誌位。當發生中斷時, INT 引腳將被MCP2515 拉為低電平, 並保持低電平狀態直至 MCU 清除中斷。中斷只有在引起相應中斷的條件消失後,才會被清除。

建議在對 CANINTF 暫存器中的標誌位進行復位操作時,採用位修改命令而不要使用一般的寫操作。這是為了避免在寫命令執行過程中無意間修改了標誌位,進而導致中斷丟失。

應該注意的是, CANINTF 中的中斷標誌位是可讀寫位,因此在相關 CANINTE 中斷使能位置 1 的前提下,對上述任一位置 1 均可使 MCU 產生中斷請求。

2. 從接收緩衝區讀走資料

rx_buff[i]= read_byte_2515(0x66+i);

3. 軟復位

soft_reset();

4. 向中斷標誌暫存器0x2c寫入位掩碼

bit_modify_2515(0x2c,0x01,0x00);//修改bit 0

5.清中斷

write_byte_2515(0x2c, 0x00);

最終操作程式碼如下

節省篇幅,重複函式不貼了。

#define CNF1_20K     0xd3        //4   20(1+4+8+7) 
#define CNF2_20K     0xfb          
#define CNF3_20K     0x46  
         
void  Init_can(void)
{
    reset_2515(); //復位
    write_byte_2515(0x0f, 0x80); //CANCTRL暫存器--進入配置模式 中文DATASHEET 58頁
	//可以設定的波特率 5K 10K 15K 20K 25K 40K 50K 80K 100K 125K 200K 400K 500K 667K 800K 1M
    write_byte_2515(0x2A, CNF1_20K); //CNF1位定時配置寄器   中文DATASHEET 41-42頁
    write_byte_2515(0x29, CNF2_20K); //CNF2位定時配置寄器   中文DATASHEET 41-42頁
    write_byte_2515(0x28, CNF3_20K); //CNF3位定時配置寄器   中文DATASHEET 41-43頁
    write_byte_2515(0x2B, 0x1f);     //CANINTE中斷使能暫存器  中文DATASHEET 50 頁
    write_byte_2515(0x60, 0x60);     //RXB0CTRL接收緩衝器0 控制暫存器 中文DATASHEET 27 頁
    //write_byte_2515(0x70, 0x20);   //接收緩衝器1控制暫存器
    bit_modify_2515(0x0C, 0x0f, 0x0f); //BFPCTRL_RXnBF 引腳控制暫存器和狀態暫存器 中文DATASHEET 29 頁
    write_byte_2515(0x0f, 0x40);   //CAN控制暫存器--迴環模式,用於測試
}
void send_byte(unsigned char data)
{
	SPI2.CH_CFG |= 0x1; // enable Tx Channel
	delay(1);
	SPI2.SPI_TX_DATA = data;
	while( !(SPI2.SPI_STATUS & (0x1 << 25)) );
	SPI2.CH_CFG &= ~0x1; // disable Tx Channel
}
unsigned char recv_byte()
{
	unsigned char data;
	SPI2.CH_CFG |= 0x1 << 1; // enable Rx Channel
	delay(1);
	data = SPI2.SPI_RX_DATA;
	delay(1);
	SPI2.CH_CFG &= ~(0x1 << 1); //disable Rx Channel
	return  data;
}
void bit_modify_2515(unsigned char addr, unsigned char mask, unsigned char data)
{
//    CS_SPI = 0 ;
    slave_enable() ;
    send_byte(0x05) ;
    send_byte(addr) ;
    send_byte(mask) ;
    send_byte(data) ;
    slave_disable() ;
//    CS_SPI = 1 ;
}
void Can_send(unsigned char *tx_buff)
{
	unsigned char i;
	write_byte_2515(0x30, 0x03); //設定為傳送最高優先順序
	write_byte_2515(0x31, 0xff); //傳送緩衝器0標準識別符號高位
	write_byte_2515(0x32, 0x00); //傳送緩衝器0標準識別符號低位
	write_byte_2515(0x35, 0x08);  //傳送緩衝器0資料長度碼8位元組
	for(i = 0; i < 8; i++)
	{
		write_byte_2515(0x36+i ,tx_buff[i]); //向txb緩衝器中寫入8個位元組
//		printf("%x ",tx_buff[i]);
	}
	send_req_2515();

}

unsigned char Can_receive(unsigned char *rx_buff)
{
	unsigned char i,flag;
    flag = read_byte_2515(0x2c); //CANINTF——中斷標誌暫存器
    printf("flag=%x\n",flag);
  //  printf(" SPI2.SPI_STATUS =%x\n", SPI2.SPI_STATUS );
 //   soft_reset();
    if (flag&0x1)                //接收緩衝器0滿中斷標誌位
    {
    	for(i = 0; i < 16; i++)
		{
    		rx_buff[i]= read_byte_2515(0x66+i);
    //		printf("%x ",rx_buff[i]);
    //		printf(" SPI2.SPI_STATUS =%x\n", SPI2.SPI_STATUS );
    		soft_reset();
		}
    	bit_modify_2515(0x2c,0x01,0x00);
    	write_byte_2515(0x2c, 0x00);
		if (!(rx_buff[1]&0x08)) return(1);	  //接收標準資料幀
    }
    return(0);
}
int main(void)
{
	GPX2.CON = 0x1 << 28;
	uart_init();

	unsigned char ID[4],buff[8];            					//狀態字
	unsigned char key;
	unsigned char ret;//,j,k,ret0,ret1,ret2,ret3,ret4,ret5,ret6,ret7,ret8,ret9;
	unsigned int rx_counter;
	volatile int i=0;

	GPC1.CON = (GPC1.CON & ~0xffff0) | 0x55550;//設定IO引腳為SPI模式

/*spi clock config*/
	CLK_SRC_PERIL1 = (CLK_SRC_PERIL1 & ~(0xF<<24)) | 6<<24;// 0x6: 0110 = SCLKMPLL_USER_T 800Mhz
	CLK_DIV_PERIL2 = 19 <<8 | 3;//SPI_CLK = 800/(19+1)/(3+1)

	soft_reset();	                   // 軟復位SPI控制器
	SPI2.CH_CFG &= ~( (0x1 << 4) | (0x1 << 3) | (0x1 << 2) | 0x3);//master mode, CPOL = 0, CPHA = 0 (Format A)
	SPI2.MODE_CFG &= ~((0x3 << 17) | (0x3 << 29));   //BUS_WIDTH=8bit,CH_WIDTH=8bit
	SPI2.CS_REG &= ~(0x1 << 1);        //選擇手動選擇晶片
	mydelay_ms(10);    //延時
    Init_can();   //初始化MCP2515

    printf("\n************ SPI CAN test!! ************\n");

    while(1)
    {
		//Turn on led
		GPX2.DAT = GPX2.DAT | 0x1 << 7;
		mydelay_ms(50);

    	printf("\nplease input 8 bytes\n");

    	for(i=0;i<8;i++)
    	{
    		src[i] = getchar();
    		putc(src[i]);
    	}
    	printf("\n");

    	Can_send(src); //傳送標準幀
        mydelay_ms(100);
        ret = Can_receive(dst); //接收CAN匯流排資料
        printf("ret=%x\n",ret);
        printf("src=");
        for(i=0;i<8;i++) printf(" %x", src[i]);//將CAN總線上收到的資料發到序列口

        printf("\n");

        printf("dst=");
        for(i=0;i<8;i++) printf(" %x",dst[6+i]); //將CAN總線上收到的資料發到序列口
		printf("\n");

		//Turn off
		GPX2.DAT = GPX2.DAT & ~(0x1 << 7);
		mydelay_ms(100);
    } //while(1)

	return 0;
} //main

問題

1 什麼是feddback 時鐘?

2 Can的緩衝暫存器組 原理
更多資訊,請關注 公號 一口Linux