51入門系列教程| 協議協議(SPI篇)

上一貼中咱簡單聊了一下通信協議,特別介紹了一下IIC。這貼咱來聊聊SPI,SPI(Serial Peripheral Interface,串列外設介面),來源於Motorola。最早在其MC86HC系列的處理器上定義使用,和IIC一樣,也是系出名門。不過這貨從協議層面上比IIC簡單多了。飛利浦對IIC的協議和硬體介面做出了嚴格的要求,制定出完善的標準。而摩托羅拉對SPI則沒有做出這麼多的約束,似乎一下子就把這種介面開源出去了。所以現在流行的SPI介面,有很多類變種,有的甚至無比奇葩,下面簡單來聊聊:

先說說硬體結構,看這個圖

標準的SPI匯流排介面一般會有4根通信線路,一條匯流排上,也可以掛多個SPI從設備

Advertisements

(1)MOSI(master out slave in)

主器件輸出從器件輸入,簡單點理解就是主設備向從設備發送數據,也有些器件稱之為SDO,就是輸出的意思。

(2)MISO(master in slave out)

主器件輸入從器件輸出,也就是主設備讀取從設備的數據,另外一種稱法是SDI,也就是輸入的意思。

(3)SCLK

和IIC一樣,無論是主器件還是從器件,所有數據的傳輸都是以這條線路上的時鐘為參照,所以SPI和IIC都是屬於同步串列介面。SCK、CLK亂七八糟的叫法也不少,不過還算比較好辨認。

(4)/SSx

IIC可以通過定址來找到匯流排上的特定器件,SPI則是用這根線來使能需要操作的設備。設備多的話,也可能會使用多個IO或者數據選擇器、解碼器之類。有些器件標記為/CS之類,其實就是一個器件使能端,這4個埠是一個標準SPI的介面。可以看出來,站在主器件的立場,數據的發送和接收是兩條不同的線路。和同一時刻只能單向通行的IIC介面相比,SPI的速度能夠提升不少。以上是標準SPI物理介面的基本情況,但是現在很多器件都是標準SPI的變種。變種一、三線SPI,譬如大名鼎鼎的時鐘晶元DS1302

Advertisements

只用到了SCLK、IO和/RST三個埠,其實就是省略了標準介面中只能夠單向傳輸的MISO和MOSI。直接做成了能夠雙向傳輸的I/O,其實細細看DS1302的時序。和SPI一樣一樣的,變種二、奇葩三線SPI、五線SPI。還有很多記不起來的晶元和一些LCD屏幕,譬如一些小尺寸LCD,只需要寫數據,就可能會用到/SS、SCLK、MOSI;譬如一些多受控管腳的IC,除了/SS、SCLK、MOSI、MISO外。可能還會添加DCx管腳用於受控指令的傳輸,這裡百家爭鳴,朵朵奇葩,啥都有。變種三、Quad SPI,這個是非常有針對性的變種。就是為了數據傳輸的速度,其實Quad SPI就是標準SPI硬體介面,只不過把數據發送模式分成了多種模式。

這些模式中,可以將原本只能夠單向傳輸的MOSI和MISO都調整成雙向傳輸,就能達到數據傳輸速度加倍的效果。甚至把某些控制管腳也用於數據傳輸,那速度就是杠杠滴了。譬如一些Quad SPI Nor Flash之類的晶元,無論哪種SPI,其實它們的基本結構都是類似的。主從設備內部都是一個移位寄存器,連接起來構成一個環形的拓撲。

再來看看通信的協議,需要發送/接收的數據,都在每個SCLK的邊沿進行傳輸。傳輸的數據為8位,按位傳輸,高位在前,低位在後,在主器件產生的從器件使能信號作用下,兩個雙向移位寄存器進行數據交換。假設主機的buff=0xaa,從機的buff=0x55,上升沿發送、下降沿接收、高位先發送。每個時鐘的上升沿移動數據

是不是很簡單呀?通信時序很簡單。但是moto對時鐘SCLK做了一些比較有意思的約束,通過CPOL,CPHA對SCLK的限定,實現SPI的四種不同模式。CPOL(Clock Polarity,時鐘極性),用來設置時鐘的空閑狀態:

為1時,時鐘空閑時為高電平

為0時,時鐘空閑時為低電平

CPHA(Clock Phase,時鐘相位),用來配置數據傳輸的時鐘邊沿

為0時,在時鐘周期的第一邊沿傳輸數據(或者理解為前一邊沿)

為1時,在時鐘周期的第二邊沿傳輸數據(或者理解為後一邊沿)

模式配置
Mode 0CPOL=0,CPHA=0
Mode 1CPOL=0,CPHA=1
Mode 2CPOL=1,CPHA=0
Mode 3CPOL=1,CPHA=1

這裡值得引起注意的地方是,對第一邊沿和第二邊沿的理解。當CPOL=0,也就是時鐘空閑為低電平時,第一邊沿應該是時鐘由低電平變成高電平的邊沿,也就是第一個上升沿,那麼第二邊沿就是下降沿了。同理,當CPOL=1,也就是時鐘空閑為高電平時,第一邊沿應該是時鐘由高電平變成低電平的邊沿,也就是第一個下降沿。那麼第二邊沿就是上升沿了,

下面把四種模式的示意圖bia出來

大家可以對比文字和圖片,瞧瞧這四種模式,好了,啰嗦了半天。來點好玩的吧!手頭上SPI的器件不多,專門跑去ADI官網申請了一篇SPI介面的AD。AD7685,SPI介面的16位AD轉換器,就是這貨

電路圖

隨手畫的,勿噴啊!簡便起見,沒有使用壓隨器,直接分壓接入。不過要注意AD7685的輸入阻抗要求,數據埠SDI直接連到高電平。其實是一個三線制的SPI介面,看看AD7685的時序吧!

一次傳輸16位AD數據,在第一個上升沿進行第一位數據的傳輸。所以是Mode 0的SPI器件,也就是 CPOL=0,CPHA=0。51無需去理會CPOL和CPHA,只需要知道AD7685的要求即可,上代碼:

#include <reg51.h>

#define Vref 5.0

sbit smgbit1 = P3^7;

sbit smgbit2 = P3^6;

sbit SCK=P2^2;

sbit SDO=P2^1;

sbit CNV=P2^0;

sbit dp = P1^7;

smgdisp_cache[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,0x90};

unsigned int timer_count,Vol,count;

unsigned char temp=0;

float Voltage;

void delay_ms(unsigned int xms)

{

unsigned int i,j;

for(i=xms;i>0;i--)

{

for(j=124;j>0;j--);

}

}

unsigned int adconvert(void)

{

int result;

unsigned char i;

CNV=0;

delay_ms(2);

CNV=1;

delay_ms(2);

for(i=0;i<16;i++) //SPI的數據傳輸喲

{

SCK=1;

CNV=0;

result=result<<1;

if(SDO)

result=result|0x01;

SCK=0;

}

return(result);

}

void init()

{

smgbit1 = 0;

smgbit2 = 1;

TMOD=0x01;//設置定時器0為工作方式1

TH0=0xfc;

TL0=0X66;

EA=1;//開總中斷

ET0=1;//開定時器0中斷

TR0=1;//啟動定時器0

}

void SMG_Dis()

{

if(smgbit2){

P1 = smgdisp_cache[temp%10];

}

if(smgbit1){

P1 = smgdisp_cache[temp/10];

dp = 0;

}

}

void time0() interrupt 1

{ P1 = 0xff; //消除鬼影

timer_count++;

if(timer_count == 100)

{ count++;

Vol = adconvert();

Voltage = Vref*Vol/65535;

temp = (unsigned char)(Voltage*10.0);

timer_count = 0;

}

smgbit1 = ~smgbit1;

smgbit2 = ~smgbit2;

SMG_Dis();

TF0 = 0;

TH0 = 0xfc;

TL0 = 0x66;

}

void main()

{ init();

while(1){

}

}

採集AD值,通過數碼管顯示,數碼管用定時器掃描

GIF顯示從0-2.5v的採樣,突然想起手上還有個點陣LED模塊。

這貨好像也是個SPI介面呢

MAX7219LED驅動IC,趕緊看看數據手冊

果然,看看時序

依然還是模式0啊,寫好代碼

#include <reg51.h>

#include <intrins.h>

#define uchar unsigned char

#define uint unsigned int

//定義Max7219埠

sbit Max7219_pinCLK = P2^2;

sbit Max7219_pinCS = P2^1;

sbit Max7219_pinDIN = P2^0;

void Delay_xms(uint x)

{

uint i,j;

for(i=0;i<x;i++)

for(j=0;j<112;j++);

}

//--------------------------------------------

//功能:向MAX7219(U3)寫入位元組SPI

//入口參數:DATA

//出口參數:無

//說明:

void Write_Max7219_byte(uchar DATA)

{

uchar i;

Max7219_pinCS=0;

for(i=8;i>=1;i--)

{

Max7219_pinCLK=0;

Max7219_pinDIN=DATA&0x80;

DATA=DATA<<1;

Max7219_pinCLK=1;

}

}

//-------------------------------------------

//功能:向MAX7219寫入數據

//入口參數:address、dat

//出口參數:無

//說明:

void Write_Max7219(uchar address,uchar dat)

{

Max7219_pinCS=0;

Write_Max7219_byte(address); //寫入地址,即數碼管編號

Write_Max7219_byte(dat); //寫入數據,即數碼管顯示數字

Max7219_pinCS=1;

}

void Init_MAX7219(void)

{

Write_Max7219(0x09, 0x00); //解碼方式:BCD碼

Write_Max7219(0x0a, 0x00); //亮度

Write_Max7219(0x0b, 0x07); //掃描界限;8個數碼管顯示

Write_Max7219(0x0c, 0x01); //掉電模式:0,普通模式:1

Write_Max7219(0x0f, 0x00); //顯示測試:1;測試結束,正常顯示:0

}

void main(void)

{

uchar i,j;

uchar temp[]={0x00,0x66,0x99,0x81,0x42,0x24,0x18,0x00};//LED點陣取模,❤型

Init_MAX7219();

while(1)

{

for(i=1;i<9;i++)

Write_Max7219(i,temp[i-1]);

for(i=1;i<16;i++){

Write_Max7219(0x0a, i);

Delay_xms(50);}

for(i=15;i>0;i--){

Write_Max7219(0x0a, i);

Delay_xms(50);}

}

}

上個圖

其實代碼還是可以閃爍的,只需要向MAX7219相關寄存器通過SPI寫入值即可

SPI相比IIC而言的話簡單不少啊,今天就先到這裡。

了解更多51系列教程,請關注「雲漢電子社區」官方微信公眾號ickeybbs,或者登錄雲漢電子社區官方網站(bbs.ickey.cn)

Advertisements

你可能會喜歡