天天看點

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

一、I2C協定簡介

1、I2C實體層

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

1、在一個 I2C 通訊總線中,可連接配接多個 I2C 通訊裝置,支援多個通訊主機及多個通訊從機。

2、一個 I2C 總線隻使用兩條總線線路,一條雙向串行資料線(SDA) ,一條串行時鐘線(SCL)。

3、每個連接配接到總線的裝置都有一個獨立的位址,主機可以利用這個位址進行不同裝置之間的通路。

4、總線通過上拉電阻接到電源。當 I2C 裝置空閑時,會輸出高阻态,而當所有裝置都空閑,都輸出高阻态時,由上拉電阻把總線拉成高電平。 

2、I2C協定層 

1、I2C讀寫過程 

主機寫資料到從機: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        若配置的方向傳輸位為“寫資料”方向,廣播完位址,接收到應答信号後, 主機開始正式向從機傳輸資料,資料包的大小為 8 位,主機每發送完一個位元組資料,都要等待從機的應答信号(ACK),重複這個過程,可以向從機傳輸 N 個資料。當資料傳輸結束時,主機向從機發送一個停止傳輸信号(P),表示不再傳輸資料。 

主機由從機中讀資料: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        若配置的方向傳輸位為“讀資料”方向, 廣播完位址,接收到應答信号後, 從機開始向主機傳回資料,資料包大小也為 8 位,從機每發送完一個資料,都會等待主機的應答信号(ACK),重複這個過程,可以傳回 N 個資料,這個 N 也沒有大小限制。當主機希望停止接收資料時,就向從機傳回一個非應答信号(NACK),則從機自動停止資料傳輸。

複合通訊: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

       該傳輸過程有兩次起始信号(S)。一般在第一次傳輸中,主機通過 SLAVE_ADDRESS 尋找到從裝置後,發送一段“資料”,這段資料通常用于表示從裝置内部的寄存器或存儲器位址(注意區分它與 SLAVE_ADDRESS 的差別);在第二次的傳輸中,對該位址的内容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫位址,第二次則是讀寫的實際内容。

 2、通訊的起始和停止信号 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        當 SCL 線是高電平時 SDA 線從高電平向低電平切換,這個情況表示通訊的起始。當 SCL 是高電平時 SDA線由低電平向高電平切換,表示通訊的停止。起始和停止信号一般由主機産生。 

3、位址及資料方向

        I2C 協定規定裝置位址可以是 7 位或 10 位,實際中 7 位的位址應用比較廣泛。緊跟裝置位址的一個資料位用來表示資料傳輸方向資料方向位,第 8 位或第 11 位。資料方向位為“1”時表示主機由從機讀資料,該位為“0”時表示主機向從機寫資料。讀資料方向時,主機會釋放對 SDA 信号線的控制,由從機控制 SDA 信号線,主機接收信号,寫資料方向時, SDA 由主機控制,從機接收信号。 

4、響應 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        I2C 的資料和位址傳輸都帶響應。響應包括“應答(ACK)”和“非應答(NACK)”兩種信号。作為資料接收端時,當裝置(無論主從機)接收到 I2C 傳輸的一個位元組資料或位址後,若希望對方繼續發送資料,則需要向對方發送“應答(ACK)”信号,發送方會繼續發送下一個資料;若接收端希望結束資料傳輸,則向對方發送“非應答(NACK)”信号,發送方接收到該信号後會産生一個停止信号,結束信号傳輸。傳輸時主機産生時鐘,在第 9 個時鐘時,資料發送端會釋放 SDA 的控制權,由資料接

收端控制 SDA,若 SDA 為高電平,表示非應答信号(NACK),低電平表示應答信号(ACK)。 

二、 I2C架構

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

引腳:

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

 時鐘控制邏輯:

 SCL 線的時鐘信号,由 I2C 接口根據時鐘控制寄存器(CCR)控制。 

1、可選擇 I2C 通訊的“标準/快速”模式,這兩個模式分别 I2C 對應 100/400Kbit/s 的通訊速率。 

2、在快速模式下可選擇 SCL 時鐘的占空比,可選 Tlow/Thigh=2 或 Tlow/Thigh=16/9模式。

3、STM32的I2C外設挂載在APB1總線上,使用APB1的時鐘源PCLK1。

SCL信号線輸出時鐘計算: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

通訊過程 :

1、主發送器 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

2、主接收器 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

三、I2C初始化結構體 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

四、讀寫EEPROM 

        EEPROM晶片的裝置位址一共有 7 位,其中高 4 位固定為:1010 b,低 3 位則由 A0/A1/A2 信号線的電平決定,R/W 是讀寫方向位,與位址無關。 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        按照我們此處的連接配接, A0/A1/A2均為 0,是以 EEPROM的7位裝置位址是: 101 0000b ,即 0x50。由于 I2C 通訊時常常是位址跟讀寫方向連在一起構成一個 8 位數,且當 R/W 位為0 時,表示寫方向,是以加上 7 位位址,其值為“0xA0”,常稱該值為 I2C 裝置的“寫位址”;當 R/W 位為 1 時,表示讀方向,加上 7 位位址,其值為“0xA1”,常稱該值為“讀位址”。

        EEPROM 晶片中還有一個 WP 引腳,具有寫保護功能,當該引腳電平為高時,禁止寫入資料,當引腳為低電平時,可寫入資料,我們直接接地,不使用寫保護功能。 

1、 初始化I2C的GPIO

static void I2C_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能與 I2C 有關的時鐘 */
    RCC_APB1PeriphClockCmd ( RCC_APB1Periph_I2C1, ENABLE );
    RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, ENABLE );

    /* I2C_SCL、 I2C_SDA*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 開漏輸出
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 開漏輸出
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}
           

2 、配置I2C模式

static void I2C_Mode_Configu(void)
{
    I2C_InitTypeDef  I2C_InitStructure;

    /* I2C 配置 */
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;

    /* 高電平資料穩定,低電平資料變化 SCL 時鐘線的占空比 */
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;

    //I2C主機位址,随機
    I2C_InitStructure.I2C_OwnAddress1 =0x5F;

    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;

    /* I2C 的尋址模式 */
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

    /* 通信速率 */
    I2C_InitStructure.I2C_ClockSpeed = 400000;

    /* I2C 初始化 */
    I2C_Init(I2C1, &I2C_InitStructure);

    /* 使能 I2C */
    I2C_Cmd(I2C1, ENABLE);
}
           

3、向EEPROM寫入一個位元組的資料

EEPROM 單位元組寫入時序:

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

 EPROM 的單位元組時序規定,向它寫入資料的時候,第一個位元組為記憶體位址,第二個位元組是要寫入的資料内容。

//EEPROM記憶體256K,記憶體位址設定為8位uint8_t
void EEPROM_Byte_Write(uint8_t addr,uint8_t data)
{
	//産生起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	
	//EV5時間被檢測到,發送裝置位址
	I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
	
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//EV6事件被檢測到,發送要操作的存儲單元位址
	I2C_SendData(I2C1, addr);
	
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
	
	//EV8事件被檢測到,發送要存儲的資料
	I2C_SendData(I2C1, data);
	
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
	
	//資料傳輸完成
	I2C_GenerateSTOP(I2C1, ENABLE);
}
           

4、EEPROM的頁寫入

EEPROM 頁寫入時序: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        向連續位址寫入多個資料的時候,隻要告訴 EEPROM 第一個記憶體位址 address1,後面的資料按次序寫入到 address2、 address3… 這樣可以節省通訊的時間,加快速度。根據頁寫入時序,第一個資料被解釋為要寫入的記憶體位址 address1,後續可連續發送 n個資料,這些資料會依次寫入到記憶體中。其中 AT24C02 型号的晶片頁寫入時序最多可以一次發送 8 個資料(即 n = 8 ),該值也稱為頁大小。

//向EEPROM寫入多個位元組(頁寫入),每次寫入不能超過8個
//EEPROM記憶體256K,記憶體位址設定為8位uint8_t
void EEPROM_Page_Write(uint8_t addr,uint8_t *data,uint8_t numByteToWrite)
	{
	//産生起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//檢測EV5事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	
	//發送裝置位址
	I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
	
	//檢測EV6事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//發送要操作的存儲單元位址
	I2C_SendData(I2C1, addr);
	
	//檢測EV8事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
	
	while(numByteToWrite)
	{
		//發送要存儲的資料
		I2C_SendData(I2C1, *data);
		
		//檢測EV8事件
		while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
		
		data++;
		numByteToWrite--;
	}
	//資料傳輸完成
	I2C_GenerateSTOP(I2C1, ENABLE);
}
           

5、利用頁寫入的方式快速寫入多位元組 

首位址對齊到頁時的情況: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

首位址未對齊到頁時的情況

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 
#define I2C_PageSize 8

/**
* @brief 将緩沖區中的資料寫到 I2C EEPROM 中
* @param
* @arg pBuffer:緩沖區指針
* @arg WriteAddr:寫位址
* @arg NumByteToWrite:寫的位元組數
* @retval 無
*/
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite)
{
    uint8_t NumOfPage=0,NumOfSingle=0,Addr =0,count=0;

    /*mod 運算求餘,若 writeAddr 是 I2C_PageSize 整數倍,運算結果 Addr 值為 0*/
    Addr = WriteAddr % I2C_PageSize;

    /*差 count 個資料值,剛好可以對齊到頁位址*/
    count = I2C_PageSize - Addr;

    /*計算出要寫多少整數頁*/
    NumOfPage = NumByteToWrite / I2C_PageSize;

    /*mod 運算求餘,計算出剩餘不滿一頁的位元組數*/
    NumOfSingle = NumByteToWrite % I2C_PageSize;

    // Addr=0,則 WriteAddr 剛好按頁對齊 aligned
    // 這樣就很簡單了,直接寫就可以,寫完整頁後
    // 把剩下的不滿一頁的寫完即可
    if (Addr == 0) {
        /* 如果 NumByteToWrite < I2C_PageSize */
        if (NumOfPage == 0) {
            I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            I2C_EE_WaitForWritingEnd();
        }
        /* 如果 NumByteToWrite > I2C_PageSize */
        else {
            /*先把整數頁都寫了*/
            while (NumOfPage--) {
                I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
                I2C_EE_WaitForWritingEnd();
                WriteAddr += I2C_PageSize;
                pBuffer += I2C_PageSize;
            }
            /*若有多餘的不滿一頁的資料,把它寫完*/
            if (NumOfSingle!=0) {
                I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
                I2C_EE_WaitForWritingEnd();
            }
        }
    }
    // 如果 WriteAddr 不是按 I2C_PageSize 對齊
    // 那就算出對齊到頁位址還需要多少個資料,然後
    // 先把這幾個資料寫完,剩下開始的位址就已經對齊
    // 到頁位址了,代碼重複上面的即可
    else {
        /* 如果 NumByteToWrite < I2C_PageSize */
        if (NumOfPage== 0) {
            if (NumOfSingle > count) {
                I2C_EE_PageWrite(pBuffer, WriteAddr,NumOfSingle);
                I2C_EE_WaitForWritingEnd();
                WriteAddr += count;
                pBuffer += count;
            } 
        }
        /* 如果 NumByteToWrite > I2C_PageSize */
        else {
            /*位址不對齊多出的 count 分開處理,不加入這個運算*/
            NumByteToWrite -= count;
            NumOfPage = NumByteToWrite / I2C_PageSize;
            NumOfSingle = NumByteToWrite % I2C_PageSize;

            /*先把 WriteAddr 所在頁的剩餘位元組寫了*/
            if (count != 0) {
                I2C_EE_PageWrite(pBuffer, WriteAddr, count);
                I2C_EE_WaitForWritingEnd();

                /*WriteAddr 加上 count 後,位址就對齊到頁了*/
                WriteAddr += count;
                pBuffer += count;
            }
            /*把整數頁都寫了*/
            while (NumOfPage--) {
                I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
                I2C_EE_WaitForWritingEnd();
                WriteAddr += I2C_PageSize;
                pBuffer += I2C_PageSize;
            }
            /*若有多餘的不滿一頁的資料,把它寫完*/
            if (NumOfSingle != 0) {
                I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
                I2C_EE_WaitForWritingEnd();
            }
        }
    }
}
           

6、從EEPROM讀取資料 

EEPROM 讀取時序: 

STM32學習筆記9(I2C)一、I2C協定簡介二、 I2C架構三、I2C初始化結構體 四、讀寫EEPROM 

        從 EEPROM 讀取資料是一個複合的 I2C 時序,它實際上包含一個寫過程和一個讀過程。讀時序的第一個通訊過程中,使用 I2C 發送裝置位址尋址(寫方向),接着發送要讀取的“記憶體位址”;第二個通訊過程中,再次使用 I2C 發送裝置位址尋址,但這個時候的資料方向是讀方向;在這個過程之後, EEPROM 會向主機傳回從“記憶體位址”開始的資料,一個位元組一個位元組地傳輸,隻要主機的響應為“應答信号”,它就會一直傳輸下去,主機想結束傳輸時,就發送“非應答信号”,并以“停止信号”結束通訊,作為從機的 EEPROM也會停止傳輸。 

/**
  * @brief 從EEPROM 裡面讀取一塊資料
  * @param data:存放從 EEPROM 讀取的資料的緩沖區指針
  * @param addr:接收資料的 EEPROM 的位址
  * @param numByteToRead:要從 EEPROM 讀取的位元組數
  */
void EEPROM_Read(uint8_t addr,uint8_t *data,uint8_t numByteToRead)
{
	//産生起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//檢測EV5事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	
	//發送裝置位址
	I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
	
	//檢測EV6事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//發送要操作的存儲單元位址
	I2C_SendData(I2C1, addr);
	
	//檢測EV8事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
	
	//第二次起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//檢測EV5事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	
	//發送裝置位址   最後一位為讀   方向為接收
	I2C_Send7bitAddress(I2C1, 0xA1, I2C_Direction_Receiver);
	
	//檢測EV6事件
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);
	
	while(numByteToRead)
	{
		if(numByteToRead==1)
		{
			//如果為最後一個位元組
			I2C_AcknowledgeConfig(I2C1, DISABLE);
		}
		
		//EV7事件被檢測到,即資料寄存器有新的有效資料
		while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);
			
		//接收資料
		*data=I2C_ReceiveData(I2C1);
		
		data++;
		
		numByteToRead--;
	}
	
	//資料傳輸完成
	I2C_GenerateSTOP(I2C1, ENABLE);
	
	//使能應答,友善下次I2C傳輸
	I2C_AcknowledgeConfig(I2C1, DISABLE);
}
           

 7、狀态等待函數

void I2C_EE_WaitForWritingEnd()
{
	do
	{
		I2C_GenerateSTART(I2C1, ENABLE);
		
		//檢查EV5事件
		while(I2C_GetFlagStatus(I2C1, I2C_FLAG_SB)==RESET);
	
		I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
	}
	
	//檢查EV6事件
	while(I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR)==RESET);
	
	I2C_AcknowledgeConfig(I2C1, DISABLE);
}