STM32F767 I2C通信實驗

IIC通信實驗

IIC簡介

I2C(Inter-Integrated Circuit)字面上的意思是集成電路之間,它其實是I2C Bus簡稱,所以中文應該叫集成電路總線,它是一種串行通信總線,使用多主從架構,由飛利浦公司在1980年代為了讓主板、嵌入式系統或手機用以連接低速周邊設備而發展。I2C的正確讀法為“I平方C”("I-squared-C"),而“I二C”("I-two-C")則是另一種錯誤但被廣泛使用的讀法。自2006年11月1日起,使用I2C協議已經不需要支付專利費,但制造商仍然需要付費以獲取I2C從屬設備地址。

時序圖

為使用串行數據線(SDA)和串行時鐘線(SCL)、擁有7bit尋址空間的總線。 總線上有兩種類型角色的節點:

  • 主節點 - 產生時鐘并發起與從節點的通信
  • 從節點 - 接收時鐘并響應主節點的尋址

該總線是一種多主控總線,即可以在總線上放置任意多主節點。此外,在停止位(STOP)發出后,一個主節點也可以成為從節點,反之亦然。

總線上有四種不同的操作模式,雖然大部分設備只作為一種角色和使用其中兩種操作模式:

  • 主節點發送 - 主節點發送數據給從節點
  • 主節點接收 - 主節點接收從節點數據
  • 從節點發送 - 從節點發送數據給主節點
  • 從節點接收 - 從節點接收主節點數據

一開始,主節點處于主節點發送模式,發送起始位START),跟著發送希望與之通信的從節點的7bit位地址,最后再發送一個bit讀寫位,該數據位表示主節點想要與從節點進行讀(1)還是寫(0)操作。

如果從節點在總線上,它將以ACK字符比特位應答(低有效)該地址。主節點收到應答后,根據它發送的讀寫位,處于發送模式或者接收模式,從節點則處于對應的相反模式(接收或發送)。

地址和數據首先發送最高有效位。 起始位在SCL位高時,由SDA上電平從高變低表示;停止位在SCL為高時,由SDA上電平從低變高表示。其他SDA上的電平變化在SCL為低時發生。

如果主節點想要向從節點寫數據,它將發送一個字節,然后從節點以ACK位應答,如此重復。此時,主節點處于主節點發送模式,從節點處于從節點接收模式。

如果主節點想要讀取從節點數據,它將不斷接收從節點發送的一個個字節,在收到每個字節后發送ACK進行應答,除了接收到的最后一個字節。此時,主節點處于主節點接收模式,從節點處于從節點發送模式

此后,主節點要么發送停止位終止傳輸,要么發送另一個START比特以發起另一次傳輸(即“組合消息”)。

拓展

原始的I2C系統是在1980年代所創建的一種簡單的內部總線系統,當時主要的用途在于控制由飛利浦所生產的芯片。

  • 1992年完成了最初的標準版本發布,新增了傳輸速率為400 kbit/s的快速模式及長度為10比特的地址模式可容納最多1008個節點。
  • 1998年發布了2.0版,新增了傳輸速率為3.4Mbit/s的高速模式并為了節省能源而減少了電壓及電流的需求。
  • 2.1版則在2001年完成,這是一個對2.0版做一些小修正,
  • 3.0版于2007年發布。
  • 2012年2月13日發布Specification Rev. 新增 5-MHz的超快速模式(UFM)。
  • 2012年,第4版增加5 MHz的超快速模式(UFM),使用推挽式邏輯沒有上拉電阻新的USDA和USCS線,并增加了制造商指定的ID表。
  • 2012年,第5版修正錯誤。
  • 在2014年,第6版糾正了兩個圖。這是目前最新的標準。

實驗

信號類型及實驗

I2C總線在傳送數據過程中共有三種類型的信號,他們分別是:

  • 開始信號:SCL為高電平時,SDA由高電平向低電平跳變,開始傳輸數據。

  • 結束信號:SCL為高電平時,SDA由低電平向高電平跳變,結束傳輸數據。

  • 應答信號:接受數據的IC在接收到8bit數據后,向發送數據的IC發出特定的低電平脈沖,表示已經接受到數據。CPU向受控單元發出一個信號后,等待受控單元發出一個應答信號,CPU接收到應答信號后,根據實際情況作出是否繼續傳遞信號的判斷。若未收到應答信號,由判斷為受控單元出現故障。

這些信號中,起始信號是必需的,結束信號和應答信號,都可以不要。I2C總線時序如下圖:


總線時序

STM32F767上面板載的EEPROM(電子抹除式可復寫只讀存儲器)芯片型號為24C02。該芯片的總容量為256個字節,該芯片通過I2C總線與外部連接,我們本實驗就通過I2C來實現24C02的讀寫。

目前大部分MCU都帶有I2C總線接口,STM32F767不例外。但是,我們這里不使用STM32F767的硬件I2C來讀寫24C02,而是通過軟件模擬。ST為了規避飛利浦I2C的專利問題,將STM32的硬件I2C設計的比較復雜,而且穩定性極差,給開發帶來非常多的不便,所以這里我們并不推薦使用,有興趣的可以下來自己查資料,來研究下STM32F767的硬件I2C。

我們在這里使用了軟件來模擬I2C協議,這樣做的好處是,同一個代碼兼容所有的MCU,任何一個單片機只要有IO口,就可以很快的移植過去,而且不需要特定的IO口,只需要簡單的更改IO口的定義,就可以快速使用。而硬件I2C,則換一次MCU,基本上等于重新搞一次I2C驅動,非常之麻煩。

I2C的實驗功能簡介:開機的時候先檢測24C02是否存在,然后在主循環里面檢測兩個按鍵,其中1個按鍵(KEY1)用來執行寫入24C02操作,另外一個按鍵(KEY0)用來執行讀出操作,在LCD模塊上顯示相關信息,同時DS0閃爍,提示程序運行正常。

硬件部分

實驗需要用到指示燈DS2,以及按鍵KEY0,1和LCD顯示屏,24C02。

前面的硬件咱們都已經基本介紹過了,這里我們只簡單介紹以下24C02與STM32F767的連接,24C02的SCL與SDA分別連接在STM32F767的PH4和PH5上的,連接關系如下圖:


連接圖

軟件部分

首先來看I2C的初始化,我們要使用軟件來模擬,就要讓硬件也做出I2C硬件協議相關的工作,所以我們來操作兩個IO口來模擬I2C的SCL和SDA就行了,具體方法如下:

I2C初始化

void I2C_Init(void)
{
    GPIO_InitTypeDef I2C_Initure;
    
    __HAL_RCC_GPIOH_CLK_ENABLE();       //使能GPIOH時鐘

    //PH4,5初始化設置
    I2C_Initure.Pin  = GPIO_PIN_4 | GPIO_PIN_5;
    I2C_Initure.Mode = GPIO_MODE_OUTPUT_PP;     //推挽輸出
    I2C_Initure.Pull = GPIO_PULLUP;             //上拉
    I2C_Initure.Speed = GPIO_SPEED_FAST;        //快速
    
    HAL_GPIO_Init(GPIOH, &IC2_Initure);

    I2C_SDA(1);             //SDA線拉高
    I2C_SCL(1);             //SCL線拉高
}

我們在初始化中,將PH4,5兩個IO口設置為推挽輸出,然后拉上,并設為快速,然后調用HAL_GOIO_Init初始化函數,并且將兩條IO先的輸出電平先拉高,符合I2C協議的靜默狀態。至于后兩行代碼 I2C_SDA(),I2C_SCL 我們在對應的頭文件里面用宏函數來定于,具體如下:

#define I2C_SDA(n)  (n?HAL_GPIO_WritePin(GPIOH, GPIO_PIN_4, GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOH, GPIO_PIN_4, GPIO_PIN_RESET))
#define I2C_SCL(n)  (n?HAL_GPIO_WritePin(GPIOH, GPIO_PIN_5, GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOH, GPIO_PIN_5, GPIO_PIN_RESET))

那么這樣SDA,SCL線都已經準備好了,那么要開始發送信號吧,代碼如下:

產生I2C起始信號

軟件模擬起始信號的代碼如下:

void I2C_Strat(void)
{
    SDA_OUT();          //SDA線輸出
    I2C_SDA(1);
    I2C_SCL(1);
    delay_us(4);
    I2C_SDA(0);         //在SCL線為高電平時,SDA線拉低為起始信號
    delay_us(4);
    I2C_SCL(0);         //拉低SCL線,準備開始發送或者接收數據
}

其中函數 SDA_OUT() 同樣是一個宏函數,定義在頭文件中,具體如下:

#define SDA_OUT()   {GPIOH->MODER &= ~(0x3 << (10));GPIOH->MODER |= 0x0 << 10;}

通過函數 I2C_Start() 就可以發送一個開始信號,來發送或者接受數據了,本質上來說,就是我們使用了IO操作來模擬了I2C的開始階段的電壓跳變,非常簡單。

產生I2C停止信號

有起始后,需要來停止,代碼如下:

void I2C_Stop(void)
{
    SDA_OUT();
    I2C_SCL(0);
    I2C_SDA(0);
    delay_us(4);
    I2C_SCL(1);
    I2C_SDA(1);
}

依然遵從I2C的時序圖,在停止信號處,先讓SDA線輸出,然后將SCL和SDA線拉低,待一段時間后,再將SCL和SDA線全部拉高,回到靜默狀態。

等待應答信號

在起始信號發送了后,需要等待應答,代碼如下:

u8 I2C_Wait_Ack(void)
{
    u8 ucErrTime = 0;

    SDA_IN();           //SDA線切換為輸入模式
    I2C_SDA(1); delay_us(1);
    I2C_SCL(1); delay_us(1);
    while(READ_SDA) {
        ucErrTime++;
        if (ucErrTime > 250) {
            IC_Stop();
            return 1;
        }
    }
    I2C_SLC(0);     //時鐘線拉低
    return 0;
}

這里用到了兩個宏函數,仍然定義在頭文件當中,代碼如下:

#define SDA_IN()    {GPIOH->MODER &= ~(0x3 << 10); GPIOH->MODER |= 0x0 << 10}
#define READ_SDA    HAL_GPIO_ReadPin(GPIOH, GPIO_PIN_5)     //輸入SDA信號

這個函數也很容易理解,參照I2C的時序圖,將SDA線設置為了輸入模式,并拉高SDA線和SCL線,使用輪詢讀取PH5的電平值,但SDA線出現低電平,表示應答信號來到,拉低SCL線,return 0,表示接收應答成功。

產生應答信號

在作為接收方時,需要產生應答信號,代碼如下:

void I2C_Ack(void)
{
    I2C_SCL(0);
    SDA_OUT();
    I2C_SDA(0);
    delay_us(2);
    I2C_SCL(1);
    delay_us(2);
    I2C_SCL(0);
}

這個函數根據I2C的時序圖,將應答信號就可以發送出去了,代碼很好理解。

不產生應答信號

如果不產生應答信號,代碼如下:

void I2C_NAck(void)
{
    I2C_SCL(0);
    SDA_OUT();
    I2C_SDA(1);
    delay_us(2);
    I2C_SCL(1);
    delay_us(2);
    I2C_SCL(0);
}

和上邊的代碼反過來就行了,在SCL線拉低后,SDA繼續輸出高電平,那么就不會產生應答信號了。

I2C發送一個字節

void I2C_Send_Byte(u8 txd)
{
    u8 t;
    SDA_OUT();
    I2C_SCL(0);     //拉低時鐘開始數據發送
    for(t = 0; t < 8; t++) {
        I2C_SDA((txd & 0x80) >> 7);
        txd <<= 1;
        delay_us(2);
        I2C_SCL(1);
        delay_us(2);
        I2C_SCL(0);
        delay_us(2); 
    }
}

這個函數的設計也是相當的簡單了,一個字節是8位,用for循環,每次發送他的第8位,然后整體向左移動一位,每次發送一位后,通過調整SCL線電平來確定時序。

I2C讀取一個字節

有了發送,就相應的來接收就行,代碼如下:
u8 I2C_Read_Byte(u8 ack)
{
u8 i,receive = 0;

    SDA_IN();           //SDA線切換為輸入,來接收數據
    for(i = 0; i < 8; i++) {
        I2C_SCL(0);
        delay_us(2);
        I2C_SCL(1);
        receive <<= 1;
        if (READ_SDA) receive++;
        delay_us(1);
    }

    if (!ack) {
        I2C_NAck();     //不發送應答信號
    } else {
        I2C_Ack();      //發送ACK信號
    }
    return receive;
}

這個函數和發送字節其實沒有什么區別,就是反過來讀,然后return就行了,區別在于和用參數來確定要不要發送ack應答信號。

I2C的處理函數,就介紹完了,代碼非常簡單,就是通過IO操作來設置I2C_SDA及SCL。接下來來看下24C02的處理函數。

初始化I2C接口

void 24CXX_Init(void)
{
    I2C_Init();     //直接調用I2C初始化就行
}

在24CXX指定地址讀取一個數據

讀操作的時候,要先確定讀的地址,所以:
寫模式-->寫讀的地址-->讀模式-->讀數據


mark

代碼實現如下:

u8 24CXX_ReadOneByte(u16 ReadAdder)
{
    u8 temp = 0;

    I2C_Start();
    I2C_Send_Byte(0xa0 + ((ReadAdder / 256) << 1)); //發送器件地址0xa0,寫數據
    I2C_Wait_Ack();
    I2C_Send_Byte(ReadAdder % 256);     //發送低地址
    I2C_Wait_Ack();
    I2C_Start();
    I2C_Send_Byte(0xa1);                //進入接收模式
    I2C_Wait_Ack();
    temp = I2C_Read_Byte(0);
    I2C_Stop();                         //產生停止信號
    return temp;
}

在開始的時候,首先發送起始信號,然后將要讀取數據的地址寫入,并發送到E2PROM,分兩次,首先發送高8位,然后發送低8位,然后等待ack后,恢復到起始狀態,進入接收模式,再一個ack后,就可以讀取數據。

在24CXX指定地址寫一個數據

寫操作的時候,同樣先確定寫的地址,所以要寫模式-->寫地址-->寫數據
mark

代碼實現如下:

void 24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
    I2C_Start();
    I2C_Send_Byte(0xa0 + ((WriteAddr / 256) << 1));     //發送器件地址OXA0,寫數據
    I2C_Wait_Ack();
    I2C_Send_Byte(WriteAddr % 256);             //發送低地址
    I2C_Wait_Ack();
    I2C_Send_Byte(DataToWrite);
    I2C_Wait_Ack();
    I2C_Stop();             //產生停止信號
    delay_ms(10);
}

這樣單字節的寫或者讀非常繁瑣,那么再給他封裝一層,來個多字節讀寫,代碼如下:

void 24CXX_WriteLneByte(u16 WriteAddr, u32 DataToWrite, u8 Len)
{
    u8 t;
    for(t = 0; t < Len; t++) {
        24CXX_WriteOneByte(WriteAddr + t, (DataToWrite >> (8 * t)) & 0xff);
    }
}

u32 24CXX_ReadLenByte(u16 ReadAddr, u8 Len)
{
    u8 t;
    u32 temp = 0;
    for (t = 0; t < Len; t++) {
        temp <= 8;
        temp += 24CXX_ReadOneByte(ReadAddr + Len -t - 1);
    }
    return temp;
}

這2個函數非常好理解,就是用for循環來調用單字節讀寫函數即可。

這里最好還需要一個函數來檢測24C02的狀態,當IC出錯時能夠反饋錯誤,代碼如下:

u8 24CXX_Check(void)
{
    u8 temp;
    temp = 24CXX_ReadOneByte(255);      //避免每次開機都寫24CXX
    if (temp == 0x55) return 0;
    else {                              //排除第一次初始化
        24CXX_WriteOneByte(255, 0x55);
        temp = 24CXX_ReadOneByte(255);
        if (temp == 0x55) return 0;
    }
    return 1;
}

這個函數就是使用24XX的最后一個地址(255)來存儲標志字0x55,通過判斷0x55來看是不是24C02設備,如果這里使用的其他24C系列,需要更改這個地址。

再定義兩個在指定地址讀寫指定長度的數據的函數,代碼如下:

void 24CXX_Read(u16 ReadAddr, u8  *pBuffer, u16 NumToRead)
{
    while(NumToRead) {
        *pBuffer++ = 24Cxx_ReadOneByte(ReadAddr++);
        NumToRead--;
    }
}

void 24CXX_Write(u16 WriteAddr, u8 *pBuffer, u16 NumToWrite)
{
    while(NumToWrite--) {
        24CXX_WriteOneByte(WriteAddr, *pBuffer);
        WriteAddr++;
        pBuffer++;
    }
}

以上的代碼基本可以支持24C02了,我們的正點原子的開發板,把24C02地址引腳都設置為0。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 在使用單片機的過程中,I2C 通信可以說是最被廣泛使用和采納的協議之一,采用 I2C 協議可以占用更少的資源,鏈接...
    noparkinghere閱讀 2,208評論 0 8
  • 1、嵌入式系統的定義 (1)定義:以應用為中心,以計算機技術為基礎,軟硬件可裁剪,適應應用系統對功能、可靠性、成本...
    榮卓然閱讀 1,847評論 0 5
  • 什么是嵌入式 IEEE(Institute of Electrical and Electronics Engin...
    Leon_Geo閱讀 3,753評論 1 20
  • ???本文主要介紹嵌入式系統的一些基礎知識,希望對各位有幫助。 嵌入式系統基礎 1、嵌入式系統的定義 (1)定義:...
    OpenJetson閱讀 3,328評論 0 13
  • 善于發現別人的優點,并把它轉化成自己的長處,你就會成為聰明的人。 善于捕捉健康的元素,并把它轉化成自己的幸福,你就...
    醫成道人閱讀 196評論 0 0