目錄
一、STM32 的 I2C 特性及架構:
1、STM32 的 I2C 外設簡介:
STM32 的 I2C 外設可用作通訊的主機及從機,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、 10 位設備地址,支持 DMA 數據傳輸,並具有數據校驗功能。它的 I2C 外設還支持 SMBus2.0 協議, SMBus 協議與 I2C 類似,主要應用於筆記本電腦的電池管理中,本教程不展開,感興趣的讀者可參考《SMBus20》文檔瞭解。
2、STM32 的 I2C 通訊引腳:
3、通訊過程:
使用 I2C 外設通訊時,在通訊的不同階段它會對“狀態寄存器(SR1 及 SR2)”的不同數據位寫入參數,我們通過讀取這些寄存器標誌來了解通訊狀態。
① 主發送器
作爲 I2C 通訊的主機端時,向外發送數據時的過程。
② 主接收器:
作爲 I2C 通訊的主機端時,從外部接收數據的過程,見下圖:
二、軟件IIC的引腳初始化及IIC配置:
① 軟件IIC的引腳初始化
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H
#include "stm32f4xx.h"
#include <inttypes.h>
#define EEPROM_I2C_WR 0 /* 寫控制bit */
#define EEPROM_I2C_RD 1 /* 讀控制bit */
/* 定義I2C總線連接的GPIO端口, 用戶只需要修改下面4行代碼即可任意改變SCL和SDA的引腳 */
#define EEPROM_I2C_GPIO_PORT GPIOB /* GPIO端口 */
#define EEPROM_I2C_GPIO_CLK RCC_AHB1Periph_GPIOB /* GPIO端口時鐘 */
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6 /* 連接到SCL時鐘線的GPIO */
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7 /* 連接到SDA數據線的GPIO */
/* 定義讀寫SCL和SDA的宏,已增加代碼的可移植性和可閱讀性 */
#define EEPROM_I2C_SCL_1() GPIO_SetBits(EEPROM_I2C_GPIO_PORT, EEPROM_I2C_SCL_PIN) /* SCL = 1 */
#define EEPROM_I2C_SCL_0() GPIO_ResetBits(EEPROM_I2C_GPIO_PORT, EEPROM_I2C_SCL_PIN) /* SCL = 0 */
#define EEPROM_I2C_SDA_1() GPIO_SetBits(EEPROM_I2C_GPIO_PORT, EEPROM_I2C_SDA_PIN) /* SDA = 1 */
#define EEPROM_I2C_SDA_0() GPIO_ResetBits(EEPROM_I2C_GPIO_PORT, EEPROM_I2C_SDA_PIN) /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() GPIO_ReadInputDataBit(EEPROM_I2C_GPIO_PORT, EEPROM_I2C_SDA_PIN) /* 讀SDA口線狀態 */
void i2c_Start(void); // 開始信號
void i2c_Stop(void); // 停止信號
void i2c_SendByte(uint8_t _ucByte); // 發送一字節
uint8_t i2c_ReadByte(void); // 讀取數據
uint8_t i2c_WaitAck(void); // 等待應答
void i2c_Ack(void); // 應答信號
void i2c_NAck(void); // 非應答信號
uint8_t i2c_CheckDevice(uint8_t _Address); // 檢測I2C總線設備
#endif
/*
*********************************************************************************************************
* 函 數 名: i2c_CfgGpio
* 功能說明: 配置I2C總線的GPIO,採用模擬IO的方式實現
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
static void i2c_CfgGpio(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(EEPROM_I2C_GPIO_CLK, ENABLE); /* 打開GPIO時鐘 */
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; /* 開漏輸出 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(EEPROM_I2C_GPIO_PORT, &GPIO_InitStructure);
/* 給一個停止信號, 復位I2C總線上的所有設備到待機模式 */
i2c_Stop();
}
② IIC的配置
/*
*********************************************************************************************************
* 函 數 名: i2c_Delay
* 功能說明: I2C總線位延遲,最快400KHz
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
uint8_t i;
/*
可用邏輯分析儀測量I2C通訊時的頻率
工作條件:CPU主頻180MHz ,MDK編譯環境,1級優化
經測試,循環次數爲20~250時都能通訊正常
*/
for (i = 0; i < 30; i++);
}
/*
*********************************************************************************************************
* 函 數 名: i2c_Start
* 功能說明: CPU發起I2C總線啓動信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 當SCL高電平時,SDA出現一個下跳沿表示I2C總線啓動信號 */
EEPROM_I2C_SDA_1();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 數 名: i2c_Start
* 功能說明: CPU發起I2C總線停止信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 當SCL高電平時,SDA出現一個上跳沿表示I2C總線停止信號 */
EEPROM_I2C_SDA_0();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 數 名: 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)
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
if (i == 7)
{
EEPROM_I2C_SDA_1(); // 釋放總線
}
_ucByte <<= 1; /* 左移一個bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 數 名: i2c_ReadByte
* 功能說明: CPU從I2C總線設備讀取8bit數據
* 形 參:無
* 返 回 值: 讀到的數據
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 讀到第1個bit爲數據的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
EEPROM_I2C_SCL_1();
i2c_Delay();
if (EEPROM_I2C_SDA_READ())
{
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/*
*********************************************************************************************************
* 函 數 名: i2c_WaitAck
* 功能說明: CPU產生一個時鐘,並讀取器件的ACK應答信號
* 形 參:無
* 返 回 值: 返回0表示正確應答,1表示無器件響應
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
EEPROM_I2C_SDA_1(); /* CPU釋放SDA總線 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU驅動SCL = 1, 此時器件會返回ACK應答 */
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) /* CPU讀取SDA口線狀態 */
{
re = 1;
}
else
{
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
/*
*********************************************************************************************************
* 函 數 名: i2c_Ack
* 功能說明: CPU產生一個ACK信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Ack(void)
{
EEPROM_I2C_SDA_0(); /* CPU驅動SDA = 0 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU產生1個時鐘 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU釋放SDA總線 */
}
/*
*********************************************************************************************************
* 函 數 名: i2c_NAck
* 功能說明: CPU產生1個NACK信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_NAck(void)
{
EEPROM_I2C_SDA_1(); /* CPU驅動SDA = 1 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU產生1個時鐘 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 數 名: i2c_CheckDevice
* 功能說明: 檢測I2C總線設備,CPU向發送設備地址,然後讀取設備應答來判斷該設備是否存在
* 形 參:_Address:設備的I2C總線地址
* 返 回 值: 返回值 0 表示正確, 返回1表示未探測到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_CfgGpio(); /* 配置GPIO */
i2c_Start(); /* 發送啓動信號 */
/* 發送設備地址+讀寫控制bit(0 = w, 1 = r) bit7 先傳 */
i2c_SendByte(_Address | EEPROM_I2C_WR);
ucAck = i2c_WaitAck(); /* 檢測設備的ACK應答 */
i2c_Stop(); /* 發送停止信號 */
return ucAck;
}
三、利用IIC寫EEPROM的讀寫函數:
#ifndef __I2C_EE_H
#define __I2C_EE_H
#include "stm32f4xx.h"
/*
* AT24C02 2kb = 2048bit = 2048/8 B = 256 B
* 32 pages of 8 bytes each
*
* Device Address
* 1 0 1 0 A2 A1 A0 R/W
* 1 0 1 0 0 0 0 0 = 0XA0
* 1 0 1 0 0 0 0 1 = 0XA1
*/
/* AT24C01/02每頁有8個字節
* AT24C04/08A/16A每頁有16個字節
*/
#define EEPROM_DEV_ADDR 0xA0 /* 24xx02的設備地址 */
#define EEPROM_PAGE_SIZE 8 /* 24xx02的頁面大小 */
#define EEPROM_SIZE 256 /* 24xx02總容量 */
uint8_t ee_CheckOk(void); // 串行EERPOM是否正常
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize); // 從串行EEPROM指定地址處開始讀取若干數據
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize); // 向串行EEPROM指定地址寫入若干數據
void ee_Erase(void); // 擦除函數
#endif /* __I2C_EE_H */
/*
*********************************************************************************************************
* 函 數 名: ee_CheckOk
* 功能說明: 判斷串行EERPOM是否正常
* 形 參:無
* 返 回 值: 1 表示正常, 0 表示不正常
*********************************************************************************************************
*/
uint8_t ee_CheckOk(void)
{
if (i2c_CheckDevice(EEPROM_DEV_ADDR) == 0)
{
return 1;
}
else
{
/* 失敗後,切記發送I2C總線停止信號 */
i2c_Stop();
return 0;
}
}
/*
*********************************************************************************************************
* 函 數 名: ee_ReadBytes
* 功能說明: 從串行EEPROM指定地址處開始讀取若干數據
* 形 參:_usAddress : 起始地址
* _usSize : 數據長度,單位爲字節
* _pReadBuf : 存放讀到的數據的緩衝區指針
* 返 回 值: 0 表示失敗,1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
/* 採用串行EEPROM隨即讀取指令序列,連續讀取若干字節 */
/* 第1步:發起I2C總線啓動信號 */
i2c_Start();
/* 第2步:發起控制字節,高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此處是寫指令 */
/* 第3步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應答 */
}
/* 第4步:發送字節地址,24C02只有256字節,因此1個字節就夠了,如果是24C04以上,那麼此處需要連發多個地址 */
i2c_SendByte((uint8_t)_usAddress);
/* 第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應答 */
}
/* 第6步:重新啓動I2C總線。前面的代碼的目的向EEPROM傳送地址,下面開始讀取數據 */
i2c_Start();
/* 第7步:發起控制字節,高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD); /* 此處是讀指令 */
/* 第8步:發送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應答 */
}
/* 第9步:循環讀取數據 */
for (i = 0; i < _usSize; i++)
{
_pReadBuf[i] = i2c_ReadByte(); /* 讀1個字節 */
/* 每讀完1個字節後,需要發送Ack, 最後一個字節不需要Ack,發Nack */
if (i != _usSize - 1)
{
i2c_Ack(); /* 中間字節讀完後,CPU產生ACK信號(驅動SDA = 0) */
}
else
{
i2c_NAck(); /* 最後1個字節讀完後,CPU產生NACK信號(驅動SDA = 1) */
}
}
/* 發送I2C總線停止信號 */
i2c_Stop();
return 1; /* 執行成功 */
cmd_fail: /* 命令執行失敗後,切記發送停止信號,避免影響I2C總線上其他設備 */
/* 發送I2C總線停止信號 */
i2c_Stop();
return 0;
}
/*
*********************************************************************************************************
* 函 數 名: ee_WriteBytes
* 功能說明: 向串行EEPROM指定地址寫入若干數據,採用頁寫操作提高寫入效率
* 形 參:_usAddress : 起始地址
* _usSize : 數據長度,單位爲字節
* _pWriteBuf : 存放讀到的數據的緩衝區指針
* 返 回 值: 0 表示失敗,1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t usAddr;
/*
寫串行EEPROM不像讀操作可以連續讀取很多字節,每次寫操作只能在同一個page。
對於24xx02,page size = 8
簡單的處理方法爲:按字節寫操作模式,沒寫1個字節,都發送地址
爲了提高連續寫的效率: 本函數採用page wirte操作。
*/
usAddr = _usAddress;
for (i = 0; i < _usSize; i++)
{
/* 當發送第1個字節或是頁面首地址時,需要重新發起啓動信號和地址 */
if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
{
/* 第0步:發停止信號,啓動內部寫操作 */
i2c_Stop();
/* 通過檢查器件應答的方式,判斷內部寫操作是否完成, 一般小於 10ms
CLK頻率爲200KHz時,查詢次數爲30次左右
*/
for (m = 0; m < 1000; m++)
{
/* 第1步:發起I2C總線啓動信號 */
i2c_Start();
/* 第2步:發起控制字節,高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此處是寫指令 */
/* 第3步:發送一個時鐘,判斷器件是否正確應答 */
if (i2c_WaitAck() == 0)
{
break;
}
}
if (m == 1000)
{
goto cmd_fail; /* EEPROM器件寫超時 */
}
/* 第4步:發送字節地址,24C02只有256字節,因此1個字節就夠了,如果是24C04以上,那麼此處需要連發多個地址 */
i2c_SendByte((uint8_t)usAddr);
/* 第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應答 */
}
}
/* 第6步:開始寫入數據 */
i2c_SendByte(_pWriteBuf[i]);
/* 第7步:發送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應答 */
}
usAddr++; /* 地址增1 */
}
/* 命令執行成功,發送I2C總線停止信號 */
i2c_Stop();
return 1;
cmd_fail: /* 命令執行失敗後,切記發送停止信號,避免影響I2C總線上其他設備 */
/* 發送I2C總線停止信號 */
i2c_Stop();
return 0;
}
void ee_Erase(void)
{
uint16_t i;
uint8_t buf[EEPROM_SIZE];
/* 填充緩衝區 */
for (i = 0; i < EEPROM_SIZE; i++)
{
buf[i] = 0xFF;
}
/* 寫EEPROM, 起始地址 = 0,數據長度爲 256 */
if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
{
printf("擦除eeprom出錯!\r\n");
return;
}
else
{
printf("擦除eeprom成功!\r\n");
}
}
/*--------------------------------------------------------------------------------------------------*/
static void ee_Delay(__IO uint32_t nCount) //簡單的延時函數
{
for(; nCount != 0; nCount--);
}