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 0 | CPOL=0,CPHA=0 |
Mode 1 | CPOL=0,CPHA=1 |
Mode 2 | CPOL=1,CPHA=0 |
Mode 3 | CPOL=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)