基於STM8的IIC協議--協議篇


1. 綜述

  I2C(IIC,Inter-Integrated Circuit),兩線式串行總線,由PHILIPS公司開發用於連接微控制器及其外圍設備。

  它是由數據線SDA和時鍾SCL構成的串行總線,可發送和接收數據。在CPU和被控IC之間、IC與IC之間進行雙向傳送,高速IIC總線一般可達400kbps以上。但在STM8中,400kHZ已經是最快速度了。

 

2.關於STM8S103手冊的I2C簡介

芯片手冊中只對I2C的特點進行了簡單的講解,但並未深入解析其中的過程。

 

3. I2C詳細解析

  I2C總共由五個核心函數,分別為:①起始信號②停止信號③應答信號④發送數據⑤接收數據,通過這五個核心基本函數就能於大多數的傳感進行通信了。

3.1 起始信號

  當SCL為高電平期間,SDA由高電平到低電平的跳變過程;起始信號是一種電平跳變時序信號,而不是一個電平信號,如圖虛線框所示。

 

3.2 停止信號

  當SCL為高電平期間,SDA由低電平到高電平的跳變過程;停止信號也是一種電平跳變時序信號,而不是一個電平信號,如圖虛線框所示。

 

 3.3 應答信號

  I2C的數據字節定義為8位長,對於發送端每發送1個字節后,需要將數據線(SDA)釋放,由接收端反饋一個應答信號(ACK)。應答信號為低電平時,則將其規定為有效信號(ACK簡稱應答位),表示接收端已經成功接收了該字節;應答位為高電平時,規定為非應答位(NACK),一般表示接收端沒有成功接收該字節。

  對於反饋有效應答位ACK的要求是,接收端在第9個時鍾脈沖之前的低電平期間將SDA線拉低,並且確保在該時鍾的高電平期間為穩定的低電平。如果接收端是主機,則在它接收到最后一個字節后,發送一個NACK信號,以通知發送端結束數據發送,並釋放SDA線,以便主機接收端發送一個停止信號。

 

3.4 發送數據

  在發送起始信號后開始通信,主機發送一個8位數據。然后,主機釋放SDA線並等待從從機發出得確認信號(ACK)。詳細過程請看4.3.7代碼示例。

 

3.5 接收數據

  在發送起始信號后開始通信,主機發送一個8位數據。然后,從機收到數據返回一個確認信號(ACK)給主機,這時候主機才開始接收數據,待主機接收數據完成后,發送一個NACK信號給從機,以通知接收端結束數據接收。詳細過程請看4.3.8代碼示例。

 

3.6 數據有效性

  I2C總線進行數據傳送時,時鍾信號為高電平期間,數據線上的數據必須保持穩定,只有在時鍾線上的信號為低電平期間,數據線上的高電平或低電平狀態才允許變化。

 

3.7 I2C通信總過程 

 

 

4. 例程

 

4.1 編譯環境:

  我的編譯環境是IAR,這款軟件是現在STM8的主流平台,比較推薦。不過我打算等到STCubeMX更新出比較方便的版本后再去使用Keil5,因為我在用STM32的時候就是利用Keil5,的確很方便,你們也可以學着用一下。

 

4.2 主芯片:

  我的主芯片是STM8S系列中的103,其中STM8S的003、005、和103、105,配置一樣(外設和CPU頻率,FLASH),在代碼相同的情況下均可進行燒寫。

 

4.3 代碼&解析

  I2C的基本函數代碼我已經和傳感的代碼區隔開來,可以移植,幾乎適用於市面上使用I2C驅動的傳感器。

4.3.1 SDA、SCL引角初始化

 

1   //IIC引腳 
2   GPIO_Init(IIC_SCL_GPIO_Port, IIC_SCL_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);
3   GPIO_Init(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);

 

  在引角的控制上面,我選擇了直接操作GPIO的寄存器,這樣操作比較快,雖然我們感覺不出來,但是省出來的時間越來越多了,也就能夠體現出這樣寫的好處了,不過不理解怎么用的話,也可以使用庫函數進行寫高、低電平。

  我在SDA引角初始化的時候,選擇了推挽輸出_高電平_高速,這里就有人會有疑問了,SDA是會進行接收ACK信號的,需要接收即為輸入模式,怎么這里改成輸出模式,看過我STM8_GPIO介紹的博客的小伙伴應該會想到,怎么不使用開漏輸出,這個模式既能接收也能發送。沒錯,開漏輸出模式的確可以,但我在那篇博客中也有說到,開漏輸出模式不穩定,通過示波器觀察到是斜三角的,而推挽輸出是完整的矩形。圖我就懶得去弄了 -。-  ,而如何解決推挽輸出能夠接收ACK的操作看我下一小節。

 

4.3.1 I2C結構體和引角配置

  這里的結構體是方便I2C多線程,以后需要用到多個I2C接口時候,只需要再定義多一個該結構變量,賦予其他引角便可,省去了再次編寫代碼的時間和空間。

  我在26和26行編寫了兩行代碼,分別是將SDA模式改成輸出和輸入模式,直接更改寄存器里的值就能完成實現模式的更換,想知道為什么這樣寫可以改變模式的話,可以自行百度,也可以察看相對應芯片的寄存器手冊。STM8S103中的則在6.2小節中就有介紹。因為講解起來比較麻煩,這里就不進行更深入的說明了。

 1 /* Struct --------------------------------------------------------------------*/
 2 
 3 typedef struct iic
 4 {
 5   //具體信息:引腳 讀寫判定
 6   GPIO_TypeDef * pSCL_Port;       //SCL Gpio
 7   uint8_t        uSCL_Pin;        //SCL Pin
 8   GPIO_TypeDef * pSDA_Port;       //SDA Gpio
 9   uint8_t        uSDA_Pin;        //SDA Pin
10   
11   uint8_t        uSDA_Mode_Pin_Position;//SDA Mode
12 
13 }IIC_HandleTypedef;
14 
15 
16 
17 /* Define --------------------------------------------------------------------*/
18 
19 #define IIC_SCL_1(_HANDLE_)  ( (_HANDLE_)->pSCL_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSCL_Pin))
20 #define IIC_SCL_0(_HANDLE_)  ( (_HANDLE_)->pSCL_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSCL_Pin))
21                                   
22 #define IIC_SDA_1(_HANDLE_)  ( (_HANDLE_)->pSDA_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSDA_Pin))
23 #define IIC_SDA_0(_HANDLE_)  ( (_HANDLE_)->pSDA_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSDA_Pin))
24 #define IIC_SDA_R(_HANDLE_)  ( (BitStatus)(_HANDLE_)->pSDA_Port->IDR & (_HANDLE_)->uSDA_Pin)
25 
26 #define IIC_GPIO_SDA_MODE_Opt(_HANDLE_)  (_HANDLE_)->pSDA_Port->ODR |=    (uint8_t)1<<(_HANDLE_)->uSDA_Mode_Pin_Position
27 #define IIC_GPIO_SDA_MODE_Ipt(_HANDLE_)  (_HANDLE_)->pSDA_Port->ODR &=  ~((uint8_t)1<<(_HANDLE_)->uSDA_Mode_Pin_Position)

 

 4.3.2 延時函數

  延時函數顧名思義,就單純的延時,延時時間可以根據芯片的速率調整,具體時間通過示波器或者可以觀察到脈沖的儀器進行測量即可。

  這里定義了兩個延時函數目的是在SCL低電平期間先提前改變SDA的電平,待到SDA電平穩定時,再將SCL電平改變進行讀取。

 1 void vIIC_Delay_4us(void)    
 2 {
 3   uint8_t i=3;
 4   while(i--)
 5   {
 6     asm(" NOP");asm(" NOP");asm(" NOP");asm(" NOP");
 7   }
 8     
 9 }
10 
11 void vIIC_Delay_2us(void)    
12 {
13   asm(" NOP");asm(" NOP");asm(" NOP");
14 }

 

4.3.3 IIC引角賦值&結構體參數初始化

  每次調用I2C接口時都需要對IIC的句柄進行初始化。

 1 void vIIC_Handle_Init(IIC_HandleTypedef * hIICx, GPIO_TypeDef * pSCL_Port, uint8_t uSCL_Pin, GPIO_TypeDef * pSDA_Port, uint8_t uSDA_Pin)
 2 {
 3   //GPIO 
 4   hIICx->pSCL_Port = pSCL_Port;
 5   hIICx->uSCL_Pin  = uSCL_Pin ;
 6   hIICx->pSDA_Port = pSDA_Port;
 7   hIICx->uSDA_Pin  = uSDA_Pin ;
 8     
 9     
10   switch(uSDA_Pin)
11   {
12     case GPIO_PIN_0 : hIICx->uSDA_Mode_Pin_Position = 0 ;break;
13     case GPIO_PIN_1 : hIICx->uSDA_Mode_Pin_Position = 2 ;break;
14     case GPIO_PIN_2 : hIICx->uSDA_Mode_Pin_Position = 4 ;break;
15     case GPIO_PIN_3 : hIICx->uSDA_Mode_Pin_Position = 6 ;break;
16     case GPIO_PIN_4 : hIICx->uSDA_Mode_Pin_Position = 8 ;break;
17     case GPIO_PIN_5 : hIICx->uSDA_Mode_Pin_Position = 10;break;
18     case GPIO_PIN_6 : hIICx->uSDA_Mode_Pin_Position = 12;break;
19     case GPIO_PIN_7 : hIICx->uSDA_Mode_Pin_Position = 14;break;
20         
21   }
22 }

 

4.3.4 起始信號

  這里與3.1講解的操作有點不同,就是3.1中最后沒有將SCL拉低包括在內,而為了發送數據的方便,我也將SCL在此函數中拉低了。

 1 void vIIC_Start_Signal(IIC_HandleTypedef * hIICx)
 2 {
 3     
 4   IIC_SDA_1        (hIICx);                        //拉高數據線    
 5   IIC_SCL_1        (hIICx);                        //拉高時鍾線
 6   vIIC_Delay_4us   (     );                        //延時
 7   IIC_SDA_0        (hIICx);                        //拉低數據線
 8   vIIC_Delay_4us   (     );                        //延時
 9   IIC_SCL_0        (hIICx);                        //拉低時鍾線
10   vIIC_Delay_4us   (     );                        //延時
11     
12 }

4.3.5 結束信號

  這里與3.2講解的操作也有所不同,因為在數據接收完或者是發送完成后,SDA的電平不能確定,有可能是高也有可能是低電平,但在結束信號的時候,SDA需要是低電平時候拉低SCL才能作為結束信號的開始。

 1 void vIIC_Stop_Signal(IIC_HandleTypedef * hIICx)
 2 {
 3 
 4   IIC_SDA_0        (hIICx);                        //拉低數據線
 5   vIIC_Delay_4us   (     );                        //延時
 6   IIC_SCL_1        (hIICx);                        //拉高時鍾線
 7   vIIC_Delay_4us   (     );                        //延時
 8   IIC_SDA_1        (hIICx);                        //拉高數據線
 9   vIIC_Delay_4us   (     );                        //延時
10     
11 }

 

4.3.6 應答信號(ACK)

  由於因為發送端和操作的不同,這里需要將ACK分成三種,①Ack(主動拉低SDA形成應答信號)  ②NAck(主動不拉低SDA不形成應答信號)  ③ReadAck(等待應答信號)。

  ①Ack(主動拉低SDA形成應答信號)

  該信號在你沒有讀取到最后一個數據時由主機發送,使從機繼續發送數據。

 

 1 void vIIC_Ack(IIC_HandleTypedef * hIICx)
 2 {
 3 
 4   IIC_SDA_0        (hIICx);                        //拉低數據位
 5   vIIC_Delay_2us   (     );                        //延時    
 6   IIC_SCL_1        (hIICx);                        //拉高時鍾位
 7   vIIC_Delay_4us   (     );                        //延時
 8   IIC_SCL_0        (hIICx);                        //拉低時鍾位
 9   vIIC_Delay_2us   (     );                        //延時
10     
11 }

 

②NAck(主動不拉低SDA不形成應答信號) 

  該信號在你讀取完最后一個數據時由主機發送,使從機停止發送數據。

 1 void vIIC_NAck(IIC_HandleTypedef * hIICx)
 2 {    
 3     
 4   IIC_SDA_1        (hIICx);                        //SDA拉高 不應答對方
 5   vIIC_Delay_2us        (     );
 6   IIC_SCL_1        (hIICx);
 7   vIIC_Delay_4us        (     );
 8   IIC_SCL_0        (hIICx);
 9   vIIC_Delay_2us        (     );
10     
11 }

 

③ReadAck(等待應答信號)

  該信號在主機發送完數據后等待從機應答時候使用。

 1 bool bIIC_ReadACK(IIC_HandleTypedef * hIICx) //返回為:=1有ACK,=0無ACK
 2 {                
 3   IIC_GPIO_SDA_MODE_Ipt (hIICx);                                                //將SDA的模式改成輸入模式
 4   IIC_SDA_1        (hIICx);                                          //拉高數據線
 5   vIIC_Delay_2us   (     );                                           //延時
 6   IIC_SCL_1        (hIICx);                                          //拉高時鍾線
 7   vIIC_Delay_2us   (     );                                             //延時
 8   
 9   if(IIC_SDA_R(hIICx))                                                          //判斷是否成功接收應答,如‘有’返回0,‘沒有’則返回1
10   {
11     IIC_SCL_0           (hIICx);                                                //拉低時鍾線
12     vIIC_Delay_2us      (     );                                                //延時
13     IIC_GPIO_SDA_MODE_Opt(hIICx);                                               //接收完應答后,將SDA的模式改回輸出模式
14     return FALSE;                                                               //沒有應答
15   }
16   else
17   {
18     IIC_SCL_0           (hIICx);                                                //拉低時鍾線
19     vIIC_Delay_2us      (     );                                                //延時
20     IIC_GPIO_SDA_MODE_Opt(hIICx);                                               //接收完應答后,將SDA的模式改回輸出模式
21     return TRUE;                                                                //產生應答
22   }    
23                   
24 }

 

4.3.7 發送數據

  所要發送的數據為8位,學過串口協議的應該知道按位發送,我們這里將要發送的數據進行由高到低位的一個順序發送,具體操作如下,不懂的朋友可以將以下代碼通過畫圖畫出來,以方便理解。

 1 void vIIC_SendByte(IIC_HandleTypedef * hIICx, uint8_t uSendByte)
 2 {    
 3     
 4   uint8_t i;
 5     
 6   for (i=0; i<8; i++)                                                           //循環8次
 7   {
 8     if(uSendByte & 0X80)                                                        //將發送的數據最高位與1相與,若發送的數據最高位為1,則將SDA拉高,否則拉低
 9        IIC_SDA_1    (hIICx);
10     else
11        IIC_SDA_0    (hIICx);
12     uSendByte <<= 1;                                                            //數據左移1位
13     vIIC_Delay_2us    (     );                                             //延時
14     IIC_SCL_1        (hIICx);                                                  //時鍾線拉高
15     vIIC_Delay_4us    (     );                                                  //延時
16     IIC_SCL_0        (hIICx);                                                   //時鍾線拉低
17     vIIC_Delay_2us    (     );                                                  //延時
18                 
19     }
20         
21 }

 

4.3.8 數據接收

  具體操作都寫在注釋部分,在SCL高電平時候去讀取SDA的電平。

 1 uint8_t uIIC_RecvByte(IIC_HandleTypedef * hIICx)
 2 {
 3   uint8_t i,uReceiveByte = 0;                                   
 4     
 5   IIC_GPIO_SDA_MODE_Ipt(hIICx);                                                 //將SDA的模式設置為輸入模式
 6   IIC_SDA_1        (hIICx);                                                   //拉高數據線
 7   for(i=0;i<8;i++)                                                              //進行8次的循環
 8   {   
 9     uReceiveByte <<= 1;                                                         //將接收到的數據左移
10         
11     vIIC_Delay_2us    (     );                                          //延時
12     IIC_SCL_1         (hIICx);                                              //拉高時鍾線
13     vIIC_Delay_2us    (     );                                             //延時
14         
15     if(IIC_SDA_R    (hIICx))                                                 //讀取SDA電平
16     {
17         uReceiveByte |=0x01;                                                  //若SDA電平為高則將數據的最低位或上1,即為加1;若SDA電平為低,不進行該操作,則數據最低位為0
18     }
19         
20     vIIC_Delay_2us    (     );                                               //延時
21     IIC_SCL_0         (hIICx);                                                //拉低時鍾線
22     vIIC_Delay_2us    (     );                                             //延時
23   }
24   IIC_GPIO_SDA_MODE_Opt(hIICx);                                               //將SDA的模式設置為輸出模式
25     
26   return uReceiveByte;
27     
28 }

 

5.結尾 

  I2C協議核心基本函數為以上,將所有的核心函數結合起來便可與傳感器設備進行通信了,但本博客只是單純講解了I2C協議,並未與傳感器進行通信,若理解完I2C協議后可前往下一章博客進行與傳感器通信的實踐。    

  對STM8的I2C協議講解到這里結束,感謝各位看官的點擊。

  如果覺得有所收獲請點下推薦,若認為該博客中存在錯誤的說明或者對博客中某方面有疑問請留言。

 

 

作 者:浩宇99✌
出 處:https://www.cnblogs.com/zhenghaoyu/p/10719233.html
版權聲明:本文原創發表於 博客園,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM