STM32筆記之 I2C(硬件 or 模擬實現)

寫在前面:
本文章旨在總結備份、方便以後查詢,由於是個人總結,如有不對,歡迎指正;另外,內容大部分來自網絡、書籍、和各類手冊,如若侵權請告知,馬上刪帖致歉。

 

一、I2C協議

還是一樣,在實現 I2C之前必須要了解 I2C協議的實現,請看之前的文章:UART/ USRAT、I2C、SPI通信方式掃盲,這裏就不費勁再說一遍了

 

二、AT24Cxx芯片

爲了加深對 I2C協議的理解以及操作,先來認識一下 AT24C02型號的 EEPROM芯片,因爲它是使用 I2C協議通訊的,可以結合着來了解 I2C協議,下面挑一下信息瞭解一下這芯片,具體的看 Datasheet

1、功能框圖:

2、AT24Cxx存儲

3、不同容量芯片的設備地址

4、硬件地址引腳描述

 

三、硬件的 I2C

雖說,目前大多數人都說 STM32的硬件 I2C不好用、會經常出現死機現象,但這樣並不影響我們對它的研究;當然,現在 ST官方也有給出一些解決方案,這些資料後面有鏈接給出來,好了,先來了解一下硬件結構部分吧

1、功能描述

I2C模塊接收和發送數據,並將數據從串行轉換成並行,或並行轉換成串行。可以開啓或禁止中斷。接口通過數據引腳(SDA)和時鐘引腳(SCL)連接到 I2C總線。允許連接到標準(高達 100kHz)或快速(高達 400kHz)的 I2C總線。

2、特點

● 並行總線/I2C總線協議轉換器

● 多主機功能:該模塊既可做主設備也可做從設備

● I2C主設備功能

─ 產生時鐘

─ 產生起始和停止信號

● I2C從設備功能 ─ 可編程的I2C地址檢測 ─ 可響應2個從地址的雙地址能力 ─ 停止位檢測

● 產生和檢測7位/10位地址和廣播呼叫

● 支持不同的通訊速度 ─ 標準速度(高達100 kHz) ─ 快速(高達400 kHz)

● 狀態標誌:

─ 發送器/接收器模式標誌

─ 字節發送結束標誌

─ I2C總線忙標誌

● 錯誤標誌

─ 主模式時的仲裁丟失

─ 地址/數據傳輸後的應答(ACK)錯誤

─ 檢測到錯位的起始或停止條件

─ 禁止拉長時鐘功能時的上溢或下溢

● 2箇中斷向量

─ 1箇中斷用於地址/數據通訊成功

─ 1箇中斷用於錯誤

● 可選的拉長時鐘功能

● 具單字節緩衝器的DMA

● 可配置的PEC(信息包錯誤檢測)的產生或校驗:

─ 發送模式中PEC值可以作爲最後一個字節傳輸

─ 用於最後一個接收字節的PEC錯誤校驗

● 兼容SMBus 2.0

─ 25 ms時鐘低超時延時

─ 10 ms主設備累積時鐘低擴展時間

─ 25 ms從設備累積時鐘低擴展時間

─ 帶ACK控制的硬件PEC產生/校驗

─ 支持地址分辨協議(ARP)

● 兼容SMBus

注: 不是所有產品中都包含上述所有特性。請參考相關的數據手冊,確認該產品支持的I2C功能。

3、主模式下的通訊序列圖

4、從模式下的通訊序列圖

5、硬件 I2C配置的相關代碼

在使用硬件 I2C中,官方給出的一個解決方案是 DMA + 最高中斷,其中,收發超過 1Byte數據才使用 DMA處理;在這裏,我們試着把使用 DMA跟 不使用 DMA的代碼整合一下(懶得分開了)

首先,爲了區分是否使用 DMA,我們需要一個宏來確定:#define IIC_DMA_ENABLE

接着不用說了,首要的就是配置了;這裏很簡單,我們利用宏 IIC_DMA_ENABLE來決定是否增加 DMA的配置

#if (IIC_DMA_ENABLE)

/************************************************
函數名稱 : IIC_NVIC_Config
功    能 : 配置嵌套向量中斷控制器 NVIC
參    數 : 無
返 回 值 : 無
*************************************************/
static void IIC_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Configure and enable I2C DMA TX Channel interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EE_I2C_DMA_TX_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = EE_I2C_DMA_PREPRIO;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = EE_I2C_DMA_SUBPRIO;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* Configure and enable I2C DMA RX Channel interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EE_I2C_DMA_RX_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = EE_I2C_DMA_PREPRIO;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = EE_I2C_DMA_SUBPRIO;
    NVIC_Init(&NVIC_InitStructure);
}

/************************************************
函數名稱 : IIC_DMA_Init
功    能 : 配置嵌套向量中斷控制器 NVIC
參    數 : 無
返 回 值 : 無
*************************************************/
static void IIC_DMA_Init(void)
{
    /* Enable the DMA clock */
    RCC_AHBPeriphClockCmd(EE_I2C_DMA_CLK, ENABLE);

    /* I2C DMA TX and RX channels configuration */
    s_EEDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)EE_I2C_DR_Address;
    s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)0;   /* This parameter will be configured durig communication */
    s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;    /* This parameter will be configured durig communication */
    s_EEDMA_InitStructure.DMA_BufferSize = 0xFFFF;            /* This parameter will be configured durig communication */
    s_EEDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    s_EEDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    s_EEDMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
    s_EEDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    s_EEDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    s_EEDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;		// 最高優先級
    s_EEDMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    /* I2C TX DMA Channel configuration */
    DMA_DeInit(EE_I2C_DMA_CHANNEL_TX);
    DMA_Init(EE_I2C_DMA_CHANNEL_TX, &s_EEDMA_InitStructure);

    /* I2C RX DMA Channel configuration */
    DMA_DeInit(EE_I2C_DMA_CHANNEL_RX);
    DMA_Init(EE_I2C_DMA_CHANNEL_RX, &s_EEDMA_InitStructure);

    /* Enable the DMA Channels Interrupts */
    DMA_ITConfig(EE_I2C_DMA_CHANNEL_TX, DMA_IT_TC, ENABLE);
    DMA_ITConfig(EE_I2C_DMA_CHANNEL_RX, DMA_IT_TC, ENABLE);
}

/************************************************
函數名稱 : IIC_DMA_Config
功    能 : 通訊期間的 DMA配置
參    數 : Address ---- 緩衝地址
			BufferSize ---- 緩衝數據大小
			Direction ---- EE_DIRECTION_TX or EE_DIRECTION_RX
返 回 值 : 無
*************************************************/
static void IIC_DMA_Config( uint32_t Address, uint32_t BufferSize, uint32_t Direction )
{
    /* Initialize the DMA with the new parameters */
    if (Direction == EE_DIRECTION_TX)
    {
        /* Configure the DMA Tx Channel with the buffer address and the buffer size */
        s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Address;
        s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
        s_EEDMA_InitStructure.DMA_BufferSize = (uint32_t)BufferSize;
        DMA_Init(EE_I2C_DMA_CHANNEL_TX, &s_EEDMA_InitStructure);
    }
    else
    {
        /* Configure the DMA Rx Channel with the buffer address and the buffer size */
        s_EEDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Address;
        s_EEDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        s_EEDMA_InitStructure.DMA_BufferSize = (uint32_t)BufferSize;
        DMA_Init(EE_I2C_DMA_CHANNEL_RX, &s_EEDMA_InitStructure);
    }
}

#endif /* IIC_DMA_ENABLE */

/************************************************
函數名稱 : AT24Cxx_Config
功    能 : AT24Cxx配置
參    數 : 無
返 回 值 : 無
*************************************************/
void AT24Cxx_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;

    /* AT24C_I2Cx IO Periph clock enable */
    AT24C_IO_APBxClock_FUN(AT24C_SCL_CLK | AT24C_SDA_CLK, ENABLE);

    /* AT24C_I2Cx Periph clock enable */
    AT24C_I2C_APBxClock_FUN(AT24C_I2C_CLK, ENABLE);

    /* Configure AT24C_I2Cx pins: SCL, SDA */
    /* Confugure SCL and SDA pins as Alternate Function Open Drain Output */
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;

    GPIO_InitStructure.GPIO_Pin = AT24C_SCL_PINS;
    GPIO_Init(AT24C_SCL_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = AT24C_SDA_PINS;
    GPIO_Init(AT24C_SDA_PORT, &GPIO_InitStructure);

#if IIC_DMA_ENABLE
    /* DMA selective data processing */
    IIC_NVIC_Config();
    IIC_DMA_Init();

#endif /* IIC_DMA_ENABLE */

    /* AT24C_I2Cx configuration */
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED;
    I2C_Init(AT24C_I2Cx, &I2C_InitStructure);

    /* Enable AT24C_I2Cx */
    I2C_Cmd(AT24C_I2Cx, ENABLE);

#if IIC_DMA_ENABLE
    /* Enable the AT24C_I2Cx peripheral DMA requests */
    I2C_DMACmd(AT24C_I2Cx, ENABLE);

#endif /* IIC_DMA_ENABLE */
}

配置完成後,我們看單字節的處理(不使用 DMA)

在操作代碼前,先回過頭看下讀寫操作的時序先

單字節寫時序:

讀時序(讀時序有很多種方式,爲了方便,我們用隨機讀取的方式,這樣可以隨便定位地址):

/************************************************
函數名稱 : AT24Cxx_Write_Byte
功    能 : AT24Cxx寫一個字節
參    數 : Byte ---- 數據
			Address ---- 地址
返 回 值 : 0 / 1
*************************************************/
uint8_t AT24Cxx_Write_Byte( uint8_t Byte, uint16_t Address )
{
    /* While the bus is busy */
    AT24C_TimeOut = MAX_LONGTIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(4);
    }

    /* Send START condition */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /* Test on EV5 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(5);
    }

    /* Send EEPROM address for write */
    AT24C_TimeOut = MAX_TIME_OUT;
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter);

    /* Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(6);
    }

#ifndef AT24CXX_16BIT_ADDR

    /* Send the EEPROM's internal address to write to : only one byte Address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#else

    /* Send the EEPROM's internal address to write to : MSB of the address first */
    I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8));

    /* Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(6.5);
    }

    /*!< Send the EEPROM's internal address to write to : LSB of the address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#endif /* AT24CXX_16BIT_ADDR */

    /* Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(7);
    }

    /* Send the current byte */
    I2C_SendData(AT24C_I2Cx, Byte);

    /* Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(8);
    }

    /* Send STOP condition */
    I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

    /* If all operations OK, return 1 */
    return 1;
}

/************************************************
函數名稱 : AT24Cxx_Read_Byte
功    能 : AT24Cxx讀一個字節
參    數 : Address ---- 地址
返 回 值 : temp ---- 數據
*************************************************/
uint8_t AT24Cxx_Read_Byte( uint16_t Address )
{
    uint8_t temp;

    /*!< While the bus is busy */
    AT24C_TimeOut = MAX_LONGTIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(9);
    }

    /*!< Send START condition */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(10);
    }

    /*!< Send EEPROM address for write */
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter);

    /*!< Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(11);
    }

    /* Clear EV6 by setting again the PE bit */
    I2C_Cmd(AT24C_I2Cx, ENABLE);

#ifndef AT24CXX_16BIT_ADDR

    /*!< Send the EEPROM's internal address to read from: Only one byte address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#else

    /*!< Send the EEPROM's internal address to read from: MSB of the address first */
    I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8));

    /*!< Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(11.5);
    }

    /*!< Send the EEPROM's internal address to read from: LSB of the address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(ReadAddr & 0x00FF));

#endif /*!< AT24CXX_16BIT_ADDR */

    /*!< Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF) == RESET)
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(12);
    }

    /*!< Send STRAT condition a second time */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(13);
    }

    /*!< Send EEPROM address for read */
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Receiver);

    /* Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(14);
    }

    /* Disable Acknowledgement */
    I2C_AcknowledgeConfig(AT24C_I2Cx, DISABLE);

    /* Send STOP Condition */
    I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

    /* Test on EV7 and clear it */
    AT24C_TimeOut = MAX_LONGTIME_OUT;

    while(I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(15);
    }
    /* Read a byte from the EEPROM */
    temp = I2C_ReceiveData(AT24C_I2Cx);

    /* Enable Acknowledgement to be ready for another reception */
    I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE);

    /* If all operations OK, return receive data */
    return temp;
}

以上的代碼,具體的每步操作以及判定,其實我們按照上面的芯片對應操作時序以及主模式下的序列圖順序走就可以

好了,單字節處理完,那麼就來考慮多數據連續讀取了,畢竟,真正用的多的是大片數據讀取,下面的代碼保留了不用 DMA時也能讀取大片數據操作

頁編程:

/************************************************
函數名稱 : AT24Cxx_Page_Program
功    能 : AT24Cxx頁編程
參    數 : pBuffer ---- 數據
			Address ---- 地址
			Len ---- 長度
返 回 值 : 0 / 1
*************************************************/
uint8_t AT24Cxx_Page_Program( uint8_t *pBuffer, uint16_t Address, uint16_t Len )
{
    /* Set the pointer to the Number of data to be written. This pointer will be used
      by the DMA Transfer Completer interrupt Handler in order to reset the
      variable to 0. User should check on this variable in order to know if the
      DMA transfer has been complete or not. */
    g_EEData_WritePointer = Len;

    /* While the bus is busy */
    AT24C_TimeOut = MAX_LONGTIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(4);
    }

    /* Send START condition */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /* Test on EV5 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(5);
    }

    /* Send EEPROM address for write */
    AT24C_TimeOut = MAX_TIME_OUT;
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter);

    /* Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(6);
    }

#ifndef AT24CXX_16BIT_ADDR

    /* Send the EEPROM's internal address to write to : only one byte Address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#else

    /* Send the EEPROM's internal address to write to : MSB of the address first */
    I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8));

    /* Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(6.5);
    }

    /*!< Send the EEPROM's internal address to write to : LSB of the address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#endif /* AT24CXX_16BIT_ADDR */

    /* Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(7);
    }

#if (0 == IIC_DMA_ENABLE)
    /* While there is data to be written */
    while(Len--)
    {
        /* Send the current byte */
        I2C_SendData(AT24C_I2Cx, *pBuffer);

        /* Point to the next byte to be written */
        pBuffer++;

        /* Test on EV8 and clear it */
        AT24C_TimeOut = MAX_TIME_OUT;
        while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        {
            if(0 == (AT24C_TimeOut--))
                return TimeOut_Callback(8);
        }
    }

    /* Send STOP condition */
    I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

#else
    /* Configure the DMA Tx Channel with the buffer address and the buffer size */
    IIC_DMA_Config((uint32_t)pBuffer, Len, EE_DIRECTION_TX);

    /* Enable the DMA Tx Channel */
    DMA_Cmd(EE_I2C_DMA_CHANNEL_TX, ENABLE);

#endif /* IIC_DMA_ENABLE */

    /* If all operations OK, return 1 */
    return 1;
}

隨機讀取大片數據:

/************************************************
函數名稱 : AT24Cxx_Read_EEPROM
功    能 : 從 AT24Cxx中讀取數據塊
參    數 : pBuffer ---- 數據
			Address ---- 地址
			Len ---- 長度
返 回 值 : 0 / 1
*************************************************/
uint8_t AT24Cxx_Read_EEPROM( uint8_t *pBuffer, uint16_t Address, uint16_t Len )
{
    /* Set the pointer to the Number of data to be read. This pointer will be used
      by the DMA Transfer Completer interrupt Handler in order to reset the
      variable to 0. User should check on this variable in order to know if the
      DMA transfer has been complete or not. */
    g_EEData_ReadPointer = Len;

    /*!< While the bus is busy */
    AT24C_TimeOut = MAX_LONGTIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BUSY))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(9);
    }

    /*!< Send START condition */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(10);
    }

    /*!< Send EEPROM address for write */
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Transmitter);

    /*!< Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(11);
    }

    /* Clear EV6 by setting again the PE bit */
    I2C_Cmd(AT24C_I2Cx, ENABLE);

#ifndef AT24CXX_16BIT_ADDR

    /*!< Send the EEPROM's internal address to read from: Only one byte address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(Address & 0x00FF));

#else

    /*!< Send the EEPROM's internal address to read from: MSB of the address first */
    I2C_SendData(AT24C_I2Cx, (uint8_t)((Address & 0xFF00) >> 8));

    /*!< Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(11.5);
    }

    /*!< Send the EEPROM's internal address to read from: LSB of the address */
    I2C_SendData(AT24C_I2Cx, (uint8_t)(ReadAddr & 0x00FF));

#endif /*!< AT24CXX_16BIT_ADDR */

    /*!< Test on EV8 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF) == RESET)
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(12);
    }

    /*!< Send STRAT condition a second time */
    I2C_GenerateSTART(AT24C_I2Cx, ENABLE);

    /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(13);
    }

    /*!< Send EEPROM address for read */
    I2C_Send7bitAddress(AT24C_I2Cx, s_AT24Cxx_Addr, I2C_Direction_Receiver);

    /* Test on EV6 and clear it */
    AT24C_TimeOut = MAX_TIME_OUT;
    while(!I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    {
        if(0 == (AT24C_TimeOut--))
            return TimeOut_Callback(14);
    }

    /* While there is data to be read */
    while(Len)
    {
        if(Len == 1)
        {
            /* Disable Acknowledgement */
            I2C_AcknowledgeConfig(AT24C_I2Cx, DISABLE);

            /* Call User callback for critical section start (should typically disable interrupts) */
            EE_EnterCriticalSection_UserCallback();

            /* Send STOP Condition */
            I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

            /* Call User callback for critical section end (should typically re-enable interrupts) */
            EE_ExitCriticalSection_UserCallback();

	#if (0 == IIC_DMA_ENABLE)
        }

        /* Test on EV7 and clear it */
        AT24C_TimeOut = MAX_LONGTIME_OUT;

        while(I2C_CheckEvent(AT24C_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
        {
            if(0 == (AT24C_TimeOut--))
                return TimeOut_Callback(15);
        }

        /* Read a byte from the EEPROM */
        *pBuffer = I2C_ReceiveData(AT24C_I2Cx);

        /* Point to the next location where the byte read will be saved */
        pBuffer++;

        /* Decrement the read bytes counter */
        Len--;
    }

    /* Enable Acknowledgement to be ready for another reception */
    I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE);

	#else
            /* Wait for the byte to be received */
            AT24C_TimeOut = MAX_LONGTIME_OUT;

            while(I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_RXNE)==0)
            {
                if(0 == (AT24C_TimeOut--))
                    return TimeOut_Callback(16);
            }
            /* Read a byte from the EEPROM */
            *pBuffer = I2C_ReceiveData(AT24C_I2Cx);

            /* Decrement the read bytes counter */
            Len--;

            /* Wait to make sure that STOP control bit has been cleared */
            AT24C_TimeOut = MAX_TIME_OUT;
            while(AT24C_I2Cx->CR1 & I2C_CR1_STOP)
            {
                if(0 == (AT24C_TimeOut--))
                    return TimeOut_Callback(17);
            }

            /*!< Re-Enable Acknowledgement to be ready for another reception */
            I2C_AcknowledgeConfig(AT24C_I2Cx, ENABLE);
        }
        else
        {
            /* Configure the DMA Rx Channel with the buffer address and the buffer size */
            IIC_DMA_Config((uint32_t)pBuffer, Len, EE_DIRECTION_RX);

            /* Inform the DMA that the next End Of Transfer Signal will be the last one */
            I2C_DMALastTransferCmd(AT24C_I2Cx, ENABLE);

            /* Enable the DMA Rx Channel */
            DMA_Cmd(EE_I2C_DMA_CHANNEL_RX, ENABLE);
			
			break;
        }
    }

	#endif /* IIC_DMA_ENABLE */

    /* If all operations OK, return 1 */
    return 1;
}

既然用到了 DMA,那麼肯定要處理一下 DMA的中斷

#if (IIC_DMA_ENABLE)

/************************************************************************/
/*            STM32F10x I2C DMA Interrupt Handlers                      */
/************************************************************************/

/**
  * @brief  This function handles the DMA Tx Channel interrupt Handler.
  * @param  None
  * @retval None
  */
void EE_I2C_DMA_TX_IRQHandler(void)
{
    /* Check if the DMA transfer is complete */
    if(DMA_GetFlagStatus(EE_I2C_DMA_FLAG_TX_TC) != RESET)
    {
        /* Disable the DMA Tx Channel and Clear all its Flags */
        DMA_Cmd(EE_I2C_DMA_CHANNEL_TX, DISABLE);
        DMA_ClearFlag(EE_I2C_DMA_FLAG_TX_GL);

        /*!< Wait till all data have been physically transferred on the bus */
        AT24C_TimeOut = MAX_LONGTIME_OUT;
        while(!I2C_GetFlagStatus(AT24C_I2Cx, I2C_FLAG_BTF))
        {
            if((AT24C_TimeOut--) == 0) TimeOut_Callback(3);
        }

        /*!< Send STOP condition */
        I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

        /* Perform a read on SR1 and SR2 register to clear eventualaly pending flags */
        (void)AT24C_I2Cx->SR1;
        (void)AT24C_I2Cx->SR2;

        /* Reset the variable holding the number of data to be written */
        g_EEData_WritePointer = 0;
    }
}

/**
  * @brief  This function handles the DMA Rx Channel interrupt Handler.
  * @param  None
  * @retval None
  */
void EE_I2C_DMA_RX_IRQHandler(void)
{
    /* Check if the DMA transfer is complete */
    if(DMA_GetFlagStatus(EE_I2C_DMA_FLAG_RX_TC) != RESET)
    {
        /*!< Send STOP Condition */
        I2C_GenerateSTOP(AT24C_I2Cx, ENABLE);

        /* Disable the DMA Rx Channel and Clear all its Flags */
        DMA_Cmd(EE_I2C_DMA_CHANNEL_RX, DISABLE);
        DMA_ClearFlag(EE_I2C_DMA_FLAG_RX_GL);

        /* Reset the variable holding the number of data to be read */
        g_EEData_ReadPointer = 0;
    }
}

#endif /* IIC_DMA_ENABLE */

值得注意的是,無論是使用了 DMA還是沒用,只要是使用了硬件 I2C,那麼每次操作都必須進行忙等待檢測,而使用了 DMA,更要等待 DMA傳輸完成,代碼中的這兩個變量 __IO uint16_t g_EEData_ReadPointer;__IO uint16_t g_EEData_WritePointer;便是對 DMA傳輸完成的檢查

 

四、模擬的 I2C

模擬 I2C,我們要做的,就只是先把它的每個狀態封裝起來,然後再按照時序拼湊起來

先來引腳配置,兩個引腳都配置成開漏輸出

/************************************************
函數名稱 : Simulate_IIC_Config
功    能 : 模擬 IIC IO配置
參    數 : 無
返 回 值 : 無
*************************************************/
void Simulate_IIC_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	SL_IIC_SCL_APBxClock_FUN(SL_IIC_SCL_CLK, ENABLE);
	SL_IIC_SDA_APBxClock_FUN(SL_IIC_SDA_CLK, ENABLE);
	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	/* SCL */
	GPIO_InitStructure.GPIO_Pin = SL_IIC_SCL_PINS;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_Init(SL_IIC_SCL_PORT, &GPIO_InitStructure);
	
	/* SDA */
	GPIO_InitStructure.GPIO_Pin = SL_IIC_SDA_PINS;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_Init(SL_IIC_SDA_PORT, &GPIO_InitStructure);
}

接着是各種狀態的實現:

/************************************************
函數名稱 : IIC_Start
功    能 : IIC寫啓動
參    數 : 無
返 回 值 : 0 / 1
*************************************************/
uint8_t IIC_Start(void)
{
	IIC_SCL(HIGH);
	IIC_SDA(HIGH);
    IIC_Delay_us(WAIT_TIME);

    if(!IIC_SDA_READ)              // 回檢一次電平狀態
    {
        return 0;
    }
	IIC_SDA(LOW);
    IIC_Delay_us(WAIT_TIME);

    if(IIC_SDA_READ)               // 回檢一次電平狀態
    {
        return 0;
    }
	IIC_SCL(LOW);

    return 1;
}

/************************************************
函數名稱 : IIC_Stop
功    能 : IIC寫停止
參    數 : 無
返 回 值 : 無
*************************************************/
void IIC_Stop(void)
{
	IIC_SCL(LOW);
	IIC_SDA(LOW);
    IIC_Delay_us(WAIT_TIME);

    IIC_SCL(HIGH);
    IIC_Delay_us(WAIT_TIME);

	IIC_SDA(HIGH);
}

/************************************************
函數名稱 : IIC_Ack
功    能 : Ack應答
參    數 : 無
返 回 值 : 無
*************************************************/
void IIC_Ack(void)
{
	IIC_SDA(LOW);
    IIC_Delay_us(WAIT_TIME);

    IIC_SCL(HIGH);
    IIC_Delay_us(WAIT_TIME);

	IIC_SCL(LOW);
	IIC_Delay_us(WAIT_TIME);
	
    IIC_SDA(HIGH);
}

/************************************************
函數名稱 : IIC_UnAck
功    能 : Ack不應答
參    數 : 無
返 回 值 : 無
*************************************************/
void IIC_UnAck(void)
{
	IIC_SCL(LOW);
	IIC_SDA(HIGH);
    IIC_Delay_us(WAIT_TIME);

    IIC_SCL(HIGH);
    IIC_Delay_us(WAIT_TIME);

	IIC_SCL(LOW);
}

/************************************************
函數名稱 : IIC_Wait_Ack
功    能 : ACK等待
參    數 : 無
返 回 值 : 0 / 1
*************************************************/
uint8_t IIC_Wait_Ack(void)
{
    uint8_t time = 80;

    IIC_SCL(LOW);
    IIC_Delay_us(WAIT_TIME);

    IIC_SDA(HIGH);
    IIC_Delay_us(WAIT_TIME);

	IIC_SCL(HIGH);
    IIC_Delay_us(WAIT_TIME);

    while(IIC_SDA_READ)
    {
        time--;
        if(!time)
        {
            IIC_Stop();
            return 0;
        }
    }
	IIC_SCL(LOW);

    return 1;
}

/************************************************
函數名稱 : Write_IIC_Byte
功    能 : IIC寫一個字節
參    數 : Byte ---- 數據
返 回 值 : 無
*************************************************/
void Write_IIC_Byte( uint8_t Byte )
{
	uint8_t i;

	IIC_SCL(LOW);
	IIC_Delay_us(WAIT_TIME);

	for(i = 0;i < 8;i++)
	{
        IIC_SDA((BitAction)((Byte & 0x80) >> 7));
        Byte <<= 1;
        IIC_Delay_us(WAIT_TIME);

		IIC_SCL(HIGH);
        IIC_Delay_us(WAIT_TIME);

		IIC_SCL(LOW);
        IIC_Delay_us(WAIT_TIME);
    }
}

/************************************************
函數名稱 : Read_IIC_Byte
功    能 : IIC讀一個字節
參    數 : 無
返 回 值 : temp ---- 數據
*************************************************/
uint8_t Read_IIC_Byte(void)
{
	uint8_t i;
	uint8_t temp = 0;

	IIC_SDA(HIGH);
	IIC_Delay_us(WAIT_TIME);

	for(i = 0;i < 8;i++)
	{
		temp <<= 1;

		IIC_SCL(LOW);
		IIC_Delay_us(WAIT_TIME);

		IIC_SCL(HIGH);
        IIC_Delay_us(WAIT_TIME);

	#if 0
		if(IIC_SDA_READ)
		{
			temp++;
		}
	
	#else
		temp |= IIC_SDA_READ;
		
	#endif
    }
	IIC_SCL(LOW);
	IIC_Delay_us(WAIT_TIME);
	
	return temp;
}

最後,只需要把上面的狀態,對應着芯片給出的操作時序圖,封裝起來就好了

例如讀寫一個字節:

/************************************************
函數名稱 : EE_Write_Byte
功    能 : EEPROM模擬寫一個字節
參    數 : DeveiceAddr ---- 設備地址
			Data ---- 數據
			WordAddr ---- 字地址
返 回 值 : 0 / 1
*************************************************/
uint8_t EE_Write_Byte( uint8_t DeveiceAddr, uint8_t Data, uint16_t WordAddr )
{
	if(IIC_Start())
	{
		Write_IIC_Byte(DeveiceAddr | AT24C_WRITE);
		if(0 == IIC_Wait_Ack())
			goto WriteFail;
		
		Write_IIC_Byte((uint8_t)(WordAddr & 0x00FF));
		if(0 == IIC_Wait_Ack())
			goto WriteFail;
		
		Write_IIC_Byte(Data);
		if(0 == IIC_Wait_Ack())
			goto WriteFail;
		
		IIC_Stop();
		return 1;
	}
	
WriteFail:
	IIC_Stop();
	return 0;
}

/************************************************
函數名稱 : EE_Read_Byte
功    能 : EEPROM模擬讀一個字節
參    數 : DeveiceAddr ---- 設備地址
			WordAddr ---- 字地址
返 回 值 : data ---- 數據
*************************************************/
uint8_t EE_Read_Byte( uint8_t DeveiceAddr, uint16_t WordAddr )
{
	uint8_t data = 0;
	
	if(IIC_Start())
	{
		Write_IIC_Byte(DeveiceAddr | AT24C_WRITE);
		if(0 == IIC_Wait_Ack())
			goto ReadFail;
		
		Write_IIC_Byte((uint8_t)(WordAddr & 0x00FF));
		if(0 == IIC_Wait_Ack())
			goto ReadFail;
		
		if(IIC_Start())
		{
			Write_IIC_Byte(DeveiceAddr | AT24C_READ);
			if(0 == IIC_Wait_Ack())
				goto ReadFail;
			
			data = Read_IIC_Byte();
			IIC_UnAck();
			
			IIC_Stop();
			return data;
		}
	}
	
ReadFail:
	IIC_Stop();
	return 0;
}

 

五、AT24C02測試操作

/************************************************
函數名稱 : EE_Test
功    能 : EEPROM測試函數
參    數 : 無
返 回 值 : 無
*************************************************/
static uint8_t EE_Test(void)
{
	
#if _EE_TEST
	uint8_t I2c_Buf_Write[256] = {0};
	uint8_t I2c_Buf_Read[256] = {0};
	uint16_t i;

	/* 單字節讀寫測試 */
	AT24C_DUBUG_PRINTF("單字節讀寫測試\n");
	if(AT24Cxx_Write_Byte(0xBB, USE_TEST_ADDR + 0x200))
	{
		AT24Cxx_Busy_Wait();

#if USE_SIMULATE_IIC
		Delay_us(0x6FFFF);		// 模擬讀寫需要等待一段時間,否則會出錯

#endif /* USE_SIMULATE_IIC */
		
		AT24C_DUBUG_PRINTF("data:0x%02X\n",AT24Cxx_Read_Byte(USE_TEST_ADDR + 0x200));
		AT24Cxx_Busy_Wait();
		GPIO_ResetBits(GPIOB, GPIO_Pin_0);
	}

#if 1
	/* 單字節讀寫測試 */
	AT24C_DUBUG_PRINTF("頁讀寫測試\n");

	for (i = 0;i < 256;i++) // 填充緩衝
	{
		I2c_Buf_Write[i] = i;
	}
	
	I2c_Buf_Write[0] = 0xAA;

	// 將 I2c_Buf_Write中順序遞增的數據寫入 EERPOM中
	AT24Cxx_Write_EEPROM(I2c_Buf_Write, USE_TEST_ADDR, 256);
	
	// 寫入函數已經包含了busy檢查了
//	EE_DMA_RxWait();
//	AT24Cxx_Busy_Wait();

#if USE_SIMULATE_IIC
	Delay_us(0x6FFFF);			// 模擬讀寫需要等待一段時間,否則會出錯

#endif /* USE_SIMULATE_IIC */
	
	// 將 EEPROM讀出數據順序保持到 I2c_Buf_Read中
	AT24Cxx_Read_EEPROM(I2c_Buf_Read, USE_TEST_ADDR, 256);
		
	EE_DMA_RxWait();
	AT24Cxx_Busy_Wait();
   
	// 將 I2c_Buf_Read中的數據通過串口打印
	for (i = 0;i < 256;i++)
	{
		if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
		{
			AT24C_DUBUG_PRINTF("0x%02X , i = %d\n", I2c_Buf_Read[i], i);
			AT24C_DUBUG_PRINTF("錯誤:I2C EEPROM寫入與讀出的數據不一致\n\r");
			return 0;
		}
		printf("0x%02X ", I2c_Buf_Read[i]);
		if(i%11 == 10 || i == 255)
			printf("\n\r");    
	}
	AT24C_DUBUG_PRINTF("I2C(AT24C02)讀寫測試成功\n\r");

#endif
	
#endif /* _EE_TEST */
	
	return 1;
}

 

代碼:https://github.com/liziyuan970528/AT24Cxx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章