1 前言
作為一個電子設計愛好者,平時喜歡搗鼓一些小玩意小工具,無論是作為一種樂趣或者是真的能用在平時的工作學習中,都是一件很有意思的事情。我們在電子設計中經常會使用到陀螺儀、OLED液晶屏這樣的模塊,將他們進行各種組合來構思我們自己的小創意。這些模塊需要與主控芯片進行通訊,我們都知道,兩台“設備”需要進行通訊,需要有一個通訊協議,否則通訊就難以進行。常用的器件之間的通訊協議有IIC通訊協議和SPI通訊協議,我們經常使用,但是他們具體是什么含義,協議內容是什么,分別有什么特點?可能你也和我一樣,用的時候直接拿以前整理的代碼Ctrl+C,Ctrl+V,然后修改端口號就直接用了。真讓來說個子午寅卯來,還真不容易把這件事說明白,所以這里對SPI和IIC進行一個小總結,理一理思路,當別人問起來的時候也能具體講兩句。
2 IIC通訊協議具體內容
2.1 IIC通訊協議
IIC全稱為Inter-Intergrated Circuit,是Philips公司於1980年代提出並發展起來的,用於主控芯片和外圍設備之間進行低速通訊,IIC串行總線一般包括兩根信號線,一根為數據信號線SDA,一根為時鍾信號線SCL。IIC通訊屬於半雙工同步通訊方式。
單工通訊:只能一台設備發,另一台設備接收,例如遙控飛機,遙控車等。
半雙工通訊:設備可以接收也可以發送,但是發的時候不能接收,接收的時候不能發送。
雙工通訊:設備在發送的同時也可以接收,接收的同時也能發送數據。
2.1.1 IIC結構組成
IIC串行總線有兩根信號線,一根為數據信號線SDA,一根為時鍾信號線SCL,時鍾信號一般由主控芯片產生。所有掛載在IIC總線上的設備都需要接入SDA和SCL線,同時掛載到總線上的每一個設備都有唯一的地址。
Fig1 IIC總線結構
2.1.2 IIC通訊協議的特點
(1)結構簡單,通訊有效
IIC總線只需要兩根線連接控制器和外圍設備,因此占據空間非常小,只需要占用芯片的兩個管腳即可高效通訊。同時IIC總線的有效通訊距離是25英尺(7.62米),並能以10Kbps支持40個設備進行通訊。
(2)多主控
其中任何掛載在總線上並能夠進行發送個接收的設備都可以控制總線的數據傳輸和時鍾頻率,但是在某一時刻總線上只能有一個主控。
2.1.3 IIC協議內容
三種信號類型:起始信號、截止信號、應答信號
起始信號:時鍾信號SCL為高電平時,數據信號SDA從高電平跳變為低電平,此時判定為通訊開始。
終止信號:時鍾信號SCL為高電平時,數據信號SDA從低電平跳變為高電平,此時判定為通訊結束。
Fig2 IIC起始信號和截止信號
應答信號:發送端每發送一個字節數據(八位)之后的一個時鍾周期釋放數據信號線SDA,由接收端反饋一個應答信號。
當應答信號為低電平時,規定為應答(ACK),表示接收端已經成功接收到這一個字節數據。
當應答信號為高電平時,規定為非應答(NACK),表示接收端未成功接收到這一個字節數據。
Fig3 IIC應答與非應答
空閑狀態:當IIC總線的SDA信號線號SCL信號線均處於高電平狀態時,規定為總線的空閑狀態。掛載在總線上的設備釋放總線,設備內的輸出級場效應管處於截止狀態。
數據發送:掛載在IIC總線上的每一個設備都可以作為主設備,也可以作為從設備,但不能同時既是主設備又是從設備,因此IIC通訊協議屬於半雙工通訊協議。每一個設備都會對應一個唯一的地址,主從設備通過這個地址進行尋址訪問。
確定目的主機:同時,協議規定主設備在發送有效數據之前需要指定從設備的地址,因此第一段數據包為地址包。大多是設備地址為7位,而發送端一開始是發送一個字節數據(八位)之后釋放總線等待應答,因此主設備發送7位地址信號之后在最低位添加一位表示數據傳輸的方向,0表示主設備發,從設備收(主設備向從設備寫數據),1表示主設備收,從設備發(主設備向從設備讀設備)。
Fig4 數據發送序列示意圖
注:當傳輸一個字節數據之后,主機釋放SDA信號線,SDA信號線被上拉為高電平,從機接收到信號之后將SDA下拉,表明收到數據,給出ACK。
2.1.4 總結
(1)IIC總線由SDA數據信號線和SCL時鍾信號線構成,兩個線接上拉電阻,IIC總線的有效通訊距離大約是25英尺(7.62米),並能以10Kbps支持40個設備進行通訊。
IIC分為軟件IIC和硬件IIC,軟件IIC無需固定端口,只需要兩個端口能輸入輸出即可實現。
(2)IIC協議的內容(三種信號一狀態):
三信號:1)起始信號:SCL為高電平,SDA由高電平跳變為低電平,此為起始信號。
2)終止信號:SCL為高電平,SDA由低電平跳變為高電平,此為終止信號。
3)應答信號:主機發送一個字節數據后,釋放總線,從機發送應答或非應答信號;拉低SDA數據信號線為ACK信號,表明收到收據,否則為NACK,表明未收到信號。
一狀態:空閑狀態:當IIC總線的SDA信號線號SCL信號線均處於高電平狀態時,規定為總線的空閑狀態。掛載在總線上的設備釋放總線,設備內的輸出級場效應管處於截止狀態。
(3)數據發送過程:
起始信號發送之后,前一個字節中包含了從機的地址信息和數據流的方向信息,前七位表示從機地址,第八位數據流方向,0表示主->從,1表示從->主。
第九位主機釋放SDA總線,從機發送ACK信號或者NACK信號。
(4)數據格式
1)主 -> 從
Fig5 主機向從機寫數據
2)主 <- 從
Fig6 主機向從機讀數據
3)主 -> 從 -> 從 -> 主
Fig7 主機向從機寫數據之后再讀數據
2.2 IIC協議STM32 C語言實現
/* 定義I2C總線連接的GPIO端口, 用戶只需要修改下面4行代碼即可任意改變SCL和SDA的引腳 */ #define GPIO_PORT_I2C GPIOB #define RCC_I2C_PORT RCC_APB2Periph_GPIOB #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_SCL_1() GPIO_SetBits(GPIO_PORT_I2C, I2C_SCL_PIN) #define I2C_SCL_0() GPIO_ResetBits(GPIO_PORT_I2C, I2C_SCL_PIN) #define I2C_SDA_1() GPIO_SetBits(GPIO_PORT_I2C, I2C_SDA_PIN) #define I2C_SDA_0() GPIO_ResetBits(GPIO_PORT_I2C, I2C_SDA_PIN)
static void i2c_Delay(void) { uint8_t i; /** 來源:野火 F103-MINI STM32 開發板 * 下面的時間是通過安富萊AX-Pro邏輯分析儀測試得到的。 * CPU主頻72MHz時,在內部Flash運行, MDK工程不優化 * 循環次數為10時,SCL頻率 = 205KHz * 循環次數為7時,SCL頻率 = 347KHz, SCL高電平時間1.5us,SCL低電平時間2.87us * 循環次數為5時,SCL頻率 = 421KHz, SCL高電平時間1.25us,SCL低電平時間2.375us * IAR工程編譯效率高,不能設置為7 **/ for (i = 0; i < 10; i++); }
/* ********************************************************************************************************* * 函 數 名: i2c_Start * 功能說明: CPU發起I2C總線啟動信號 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void i2c_Start(void) { /* 當SCL高電平時,SDA出現一個下跳沿表示I2C總線啟動信號 */ I2C_SDA_1(); I2C_SCL_1(); i2c_Delay(); I2C_SDA_0(); i2c_Delay(); I2C_SCL_0(); i2c_Delay(); }
/* ********************************************************************************************************* * 函 數 名: i2c_Start * 功能說明: CPU發起I2C總線停止信號 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void i2c_Stop(void) { /* 當SCL高電平時,SDA出現一個上跳沿表示I2C總線停止信號 */ I2C_SDA_0(); I2C_SCL_1(); i2c_Delay(); I2C_SDA_1(); }
應答和非應答
/* ********************************************************************************************************* * 函 數 名: i2c_Ack * 功能說明: CPU產生一個ACK信號 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void i2c_Ack(void) { I2C_SDA_0(); /* CPU驅動SDA = 0 */ i2c_Delay(); I2C_SCL_1(); /* CPU產生1個時鍾 */ i2c_Delay(); I2C_SCL_0(); i2c_Delay(); I2C_SDA_1(); /* CPU釋放SDA總線 */ }
/* ********************************************************************************************************* * 函 數 名: i2c_NAck * 功能說明: CPU產生1個NACK信號 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void i2c_NAck(void) { I2C_SDA_1(); /* CPU驅動SDA = 1 */ i2c_Delay(); I2C_SCL_1(); /* CPU產生1個時鍾 */ i2c_Delay(); I2C_SCL_0(); i2c_Delay(); }
發送數據
主 -> 從
/* ********************************************************************************************************* * 函 數 名: i2c_SendByte * 功能說明: CPU向I2C總線設備發送8bit數據 * 形 參:_ucByte : 等待發送的字節 * 返 回 值: 無 ********************************************************************************************************* */ void i2c_SendByte(uint8_t _ucByte) { uint8_t i; /* 先發送字節的高位bit7 */ for (i = 0; i < 8; i++) { if (_ucByte & 0x80) { I2C_SDA_1(); } else { I2C_SDA_0(); } i2c_Delay(); I2C_SCL_1(); i2c_Delay(); I2C_SCL_0(); if (i == 7) { I2C_SDA_1(); // 釋放總線 } _ucByte <<= 1; /* 左移一個bit */ i2c_Delay(); } }
主 <- 從
/* ********************************************************************************************************* * 函 數 名: i2c_ReadByte * 功能說明: CPU從I2C總線設備讀取8bit數據 * 形 參:無 * 返 回 值: 讀到的數據 ********************************************************************************************************* */ uint8_t i2c_ReadByte(u8 ack) { uint8_t i; uint8_t value; /* 讀到第1個bit為數據的bit7 */ value = 0; for (i = 0; i < 8; i++) { value <<= 1; I2C_SCL_1(); i2c_Delay(); if (I2C_SDA_READ()) { value++; } I2C_SCL_0(); i2c_Delay(); } if(ack==0) i2c_NAck(); else i2c_Ack(); return value; }
等待應答信號
/* ********************************************************************************************************* * 函 數 名: i2c_WaitAck * 功能說明: CPU產生一個時鍾,並讀取器件的ACK應答信號 * 形 參:無 * 返 回 值: 返回0表示正確應答,1表示無器件響應 ********************************************************************************************************* */ uint8_t i2c_WaitAck(void) { uint8_t re; I2C_SDA_1(); /* CPU釋放SDA總線 */ i2c_Delay(); I2C_SCL_1(); /* CPU驅動SCL = 1, 此時器件會返回ACK應答 */ i2c_Delay(); if (I2C_SDA_READ()) /* CPU讀取SDA口線狀態 */ { re = 1; } else { re = 0; } I2C_SCL_0(); i2c_Delay(); return re; }
檢查總線上是否存在設備
/* ********************************************************************************************************* * 函 數 名: i2c_CheckDevice * 功能說明: 檢測I2C總線設備,CPU向發送設備地址,然后讀取設備應答來判斷該設備是否存在 * 形 參:_Address:設備的I2C總線地址 * 返 回 值: 返回值 0 表示正確, 返回1表示未探測到 ********************************************************************************************************* */ uint8_t i2c_CheckDevice(uint8_t _Address) { uint8_t ucAck; i2c_GPIO_Config(); /* 配置GPIO */ i2c_Start(); /* 發送啟動信號 */ /* 發送設備地址+讀寫控制bit(0 = w, 1 = r) bit7 先傳 */ i2c_SendByte(_Address|I2C_WR); ucAck = i2c_WaitAck(); /* 檢測設備的ACK應答 */ i2c_Stop(); /* 發送停止信號 */ return ucAck; }
3 SPI通訊協議具體內容
3.1 SPI通訊協議
SPI的全稱叫做Serial Peripheral interface的縮寫,串行外圍設備接口,是Motorla最開始在MC68HCXX系列處理器上使用,SPI接口主要應用在EEPEOM、FLASH、AD轉換器、數字信號處理器(DSP)和數字信號解碼器之間通訊使用。SPI是一種高速、全雙工和同步的通訊總線,SPI總線需要使用四根線。
四根線的定義:
Fig8 SPI硬件連接
表1 — 引腳含義圖
序號 | 引腳標號 | 說明 |
1 | SDO | 主設備數據輸出,從設備數據輸入 對應MOSI master output slave input |
2 | SDI | 主設備數據輸入,從設備數據輸出 對應MISO master input slave output |
3 | SCLK | 時鍾信號,由主設備產生 |
4 | CS | 從設備使能信號,由主設備控制 |
和IIC通訊協議相同,都包含SCLK時鍾信號,時鍾信號有主設備控制,一個SPI總線上必須有一個主設備。
3.1.1 SPI總線結構
總線結構如下所示:
Fig9 SPI總線結構
3.1.2 SPI總線通訊四種模式
SPI總線通訊定義了四種通訊模式,通過配置時鍾級性CPOL(Clock Polarity)和時鍾相位CPHA(Clock Phase )確定主設備的通訊模式,配置如下:
Mode0: CPOL=0 CPHA=0
Mode1: CPOL=0, CPHA=1
Mode2: CPOL=1, CPHA=0
Mode3: CPOL=1, CPHA=1
CPOL=0,表示當SCLK=0時處於空閑態,所以有效狀態就是SCLK處於高電平時
CPOL=1,表示當SCLK=1時處於空閑態,所以有效狀態就是SCLK處於低電平時
CPHA=0,表示數據采樣是在第1個邊沿,數據發送在第2個邊沿
CPHA=1,表示數據采樣是在第2個邊沿,數據發送在第1個邊沿
工作狀態1:
CPOL=0,CPHA=0:此時空閑態時,SCLK處於低電平,數據采樣是在第1個邊沿,也就是SCLK由低電平到高電平的跳變,所以數據采樣是在上升沿,數據發送是在下降沿。
工作狀態2:
CPOL=0,CPHA=1:此時空閑態時,SCLK處於低電平,數據發送是在第1個邊沿,也就是SCLK由低電平到高電平的跳變,所以數據采樣是在下降沿,數據發送是在上升沿。
工作狀態3:
CPOL=1,CPHA=0:此時空閑態時,SCLK處於高電平,數據采集是在第1個邊沿,也就是SCLK由高電平到低電平的跳變,所以數據采集是在下降沿,數據發送是在上升沿。
工作狀態4:
CPOL=1,CPHA=1:此時空閑態時,SCLK處於高電平,數據發送是在第1個邊沿,也就是SCLK由高電平到低電平的跳變,所以數據采集是在上升沿,數據發送是在下降沿。
Fig10 SPI總線信號周期
SPI協議沒有專門的起始信號和終止信號,而是主機控制時鍾信號線來控制通訊的起始和終止,當沒有數據交流的時候時鍾線保持低電平或者高電平。