一、環境介紹

編程軟件:keil5

操作系統:

MCU型號:

STM32編程方式: 寄存器開發 (方便程序移植到其他單片機)

IIC總線: 

模擬時序更加方便移植到其他單片機,通用性更高,不分MCU;硬件時序效率更高,單每個MCU配置方法不同,依賴硬件本身支持。

目前器件:

完整的工程源碼下載地址,下載即可編譯運行測試(包含了模擬IIC時序、STM32硬件IIC時序分別驅動AT24C02和AT24C08):  

二、AT24C02存儲芯片介紹

2.1 芯片功能特性介紹

AT24C02 是串行CMOS類型的EEPROM存儲芯片,AT24C0x這個系列包含了AT24C01、AT24C02、AT24C04、AT24C08、AT24C16這些具體的芯片型號。

他們容量分別是:1K (128 x 8)、2K (256 x 8)、4K (512 x 8)、8K (1024 x 8)、16K (2048 x 8)  ,其中的8表示8位(bit)

它們的管腳功能、封裝特點如下:

STM32CUBEMX I2C 配置教程_#單片機

STM32CUBEMX I2C 配置教程_#嵌入式_02

芯片功能描述:

AT24C02系列支持I2C,總線數據傳送協議I2C,總線協議規定任何將數據傳送到總線的器件作為發送器。任何從總線接收數據的器件為接收器;數據傳送是由產生串行時鐘和所有起始停止信號的主器件控制的。主器件和從器件都可以作為發送器或接收器,但由主器件控制傳送數據(發送或接收)的模式,由於A0、A1和A2可以組成000~111八種情況,即通過器件地址輸入端A0、A1和A2可以實現將最多8個AT24C02器件連接到總線上,通過進行不同的配置進行選擇器件。

芯片特性介紹:

1. 低壓和標準電壓運行
          –2.7(VCC=2.7伏至5.5伏)
          –1.8(VCC=1.8伏至5.5伏)

2. 兩線串行接口(SDA、SCL)

3. 有用於硬件數據保護的寫保護引腳

4. 自定時寫入週期(5毫秒~10毫秒),因為內部有頁緩衝區,向AT24C0x寫入數據之後,還需要等待AT24C0x將緩衝區數據寫入到內部EEPROM區域.

5. 數據保存可達100年

6. 100萬次擦寫週期

7. 高數據傳送速率為400KHz、低速100KHZ和IIC總線兼容。 100 kHz(1.8V)和400 kHz(2.7V、5V)

8. 8字節頁寫緩衝區
       這個緩衝區大小與芯片具體型號有關: 8字節頁(1K、2K)、16字節頁(4K、8K、16K)
      

2.2 芯片設備地址介紹

STM32CUBEMX I2C 配置教程_#IIC_03

IIC設備的標準地址位是7位。上面這個圖裏AT24C02的1010是芯片內部固定值,A2 、A1、 A0是硬件引腳、由硬件決定電平;最後一位是讀/寫位(1是讀,0是寫),讀寫位不算在地址位裏,但是根據IIC的時序順序,在操作設備前,都需要先發送7位地址,再發送1位讀寫位,才能啓動對芯片的操作,我們在寫模擬時序為了方便統一寫for循環,按字節發送,所以一般都是將7地址位與1位讀寫位拼在一起,組合成1個字節,方便按字節傳輸數據。

我現在使用的開發板上AT24C02的原理圖是這樣的:

STM32CUBEMX I2C 配置教程_#AT24C02_04

那麼這個AT24C02的標準設備地址就是: 0x50(十六進制),對應的二進制就是: 1010000

如果將讀寫位組合在一起,讀權限的設備地址: 0xA1 (10100001)  、寫權限的設備地址: 0xA0 (10100000)

 

2.3  對AT24C02 按字節寫數據的指令流程(時序)

STM32CUBEMX I2C 配置教程_#嵌入式_05

     詳細解釋:

1.  先發送起始信號

2.  發送設備地址(寫權限)

3. 等待AT24C02應答、低電平有效

4. 發送存儲地址、AT24C02內部一共有256個字節空間,尋址是從0開始的,範圍是(0~255);發送這個存儲器地址就是告訴AT24C02接下來的數據改存儲到哪個地方。

5. 等待AT24C02應答、低電平有效

6. 發送一個字節的數據,這個數據就是想存儲到AT24C02裏保存的數據。

7. 等待AT24C02應答、低電平有效

8. 發送停止信號

 

2.3  對AT24C02 按頁寫數據的指令流程(時序)

STM32CUBEMX I2C 配置教程_#嵌入式_06

 詳細解釋:

1.  先發送起始信號

2.  發送設備地址(寫權限)

3. 等待AT24C02應答、低電平有效

4. 發送存儲地址、AT24C02內部一共有256個字節空間,尋址是從0開始的,範圍是(0~255);發送這個存儲器地址就是告訴AT24C02接下來的數據改存儲到哪個地方。

5. 等待AT24C02應答、低電平有效

6. 可以循環發送8個字節的數據,這些數據就是想存儲到AT24C02裏保存的數據。

    AT24C02的頁緩衝區是8個字節,所有這裏的循環最多也只能發送8個字節,多發送的字節會將前面的覆蓋掉。

 需要注意的地方:  這個頁緩衝區的尋址也是從0開始,比如:  0~7算第1頁,8~15算第2頁......依次類推。 如果現在寫數據的起始地址是3,那麼這一頁只剩下5個字節可以寫;並不是説從哪裏都可以循環寫8個字節。

      詳細流程: 這裏程序裏一般使用for循環實現 

     (1).  發送字節1

     (2). 等待AT24C02應答,低電平有效

     (3). 發送字節2

     (4). 等待AT24C02應答,低電平有效

     .........

     最多8次.   

7. 等待AT24C02應答、低電平有效

8. 發送停止信號

2.4  從AT24C02任意地址讀任意字節數據(時序)

STM32CUBEMX I2C 配置教程_#AT24C02_07

AT24C02支持當前地址讀、任意地址讀,最常用的還是任意地址讀,因為可以指定讀取數據的地址,比較靈活,上面這個指定時序圖就是任意地址讀。

 詳細解釋:

1.  先發送起始信號

2.  發送設備地址(寫權限)

3. 等待AT24C02應答、低電平有效

4. 發送存儲地址、AT24C02內部一共有256個字節空間,尋址是從0開始的,範圍是(0~255);發送這個存儲器地址就是告訴AT24C02接下來應該返回那個地址的數據給單片機。

5. 等待AT24C02應答、低電平有效

6.  重新發送起始信號(切換讀寫模式)

7. 發送設備地址(讀權限)

8.  等待AT24C02應答、低電平有效

9. 循環讀取數據:  接收AT24C02返回的數據.

   讀數據沒有字節限制,可以第1個字節、也可以連續將整個芯片讀完。

10. 發送非應答(高電平有效)

11. 發送停止信號

三、IIC總線介紹

       2.1 IIC總線簡介

I2C(Inter-Integrated Circuit)總線是由PHILIPS公司開發的兩線式串行總線,用於連接微控制器及其外圍設備,是微電子通信控制領域廣泛採用的一種總線標準。具有接口線少,控制方式簡單,器件封裝形式小,通信速率較高等優點。

I2C規程運用主/從雙向通訊。器件發送數據到總線上,則定義為發送器,器件接收數據則定義為接收器。主器件和從器件都可以工作於接收和發送狀態。

I2C 總線通過串行數據(SDA)線和串行時鐘(SCL)線在連接到總線的器件間傳遞信息。每個器件都有一個唯一的地址識別,而且都可以作為一個發送器或接收器(由器件的功能決定)。

I2C有四種工作模式:
       1.主機發送
       2.主機接收
       3.從機發送
       4.從機接收

I2C總線只用兩根線:串行數據SDA(Serial Data)、串行時鐘SCL(Serial Clock)。

總線必須由主機(通常為微控制器)控制,主機產生串行時鐘(SCL)控制總線的傳輸方向,併產生起始和停止條件。

SDA線上的數據狀態僅在SCL為低電平的期間才能改變。

2.2 IIC總線上的設備連接圖

STM32CUBEMX I2C 配置教程_#嵌入式_08

I2C 總線在物理連接上非常簡單,分別由SDA(串行數據線)和SCL(串行時鐘線)及上拉電阻組成。通信原理是通過對SCL和SDA線高低電平時序的控制,來產生I2C總線協議所需要的信號進行數據的傳遞。在總線空閒狀態時,這兩根線一般被上面所接的上拉電阻拉高,保持着高電平。

其中上拉電阻範圍是4.7K~100K。

2.3 I2C總線特徵

I2C總線上的每一個設備都可以作為主設備或者從設備,而且每一個從設備都會對應一個唯一的地址(可以從I2C器件的數據手冊得知)。主從設備之間就通過這個地址來確定與哪個器件進行通信,在通常的應用中,我們把CPU帶I2C總線接口的模塊作為主設備,把掛接在總線上的其他設備都作為從設備。

1.    總線上能掛接的器件數量
     I2C總線上可掛接的設備數量受總線的最大電容400pF 限制,如果所掛接的是相同型號的器件,則還受器件地址的限制。
    一般I2C設備地址是7位地址(也有10位),地址分成兩部分:芯片固化地址(生產芯片時候哪些接地,哪些接電源,已經固定),可編程地址(引出IO口,由硬件設備決定)。
     例如: 某一個器件是7 位地址,其中10101 xxx  高4位出廠時候固定了,低3位可以由設計者決定。
則一條I2C總線上只能掛該種器件最少8個。
如果7位地址都可以編程,那理論上就可以達到128個器件,但實際中不會掛載這麼多。

2.    總線速度傳輸速度:
I2C總線數據傳輸速率在標準模式下可達100kbit/s,快速模式下可達400kbit/s,高速模式下可達3.4Mbit/s。一般通過I2C總線接口可編程時鐘來實現傳輸速率的調整。

3.    總線數據長度
I2C總線上的主設備與從設備之間以字節(8位)為單位進行雙向的數據傳輸。

 2.4 I2C總線協議基本時序信號

空閒狀態:SCL和SDA都保持着高電平。

起始條件:總線在空閒狀態時,SCL和SDA都保持着高電平,當SCL為高電平期間而SDA由高到低的跳變,表示產生一個起始條件。在起始條件產生後,總線處於忙狀態,由本次數據傳輸的主從設備獨佔,其他I2C器件無法訪問總線。

停止條件:當SCL為高而SDA由低到高的跳變,表示產生一個停止條件。

答應信號:每個字節傳輸完成後的下一個時鐘信號,在SCL高電平期間,SDA為低,則表示一個應答信號。

非答應信號:每個字節傳輸完成後的下一個時鐘信號,在SCL高電平期間,SDA為高,則表示一個應答信號。應答信號或非應答信號是由接收器發出的,發送器則是檢測這個信號(發送器,接收器可以從設備也可以主設備)。

注意:起始和結束信號總是由主設備產生。

 

2.5  起始信號與停止信號

起始信號就是:  時鐘線SCL處於高電平的時候,數據線SDA由高電平變為低電平的過程。SCL=1;SDA=1;SDA=0;

停止信號就是: 時鐘線SCL處於低電平的時候,  數據線SDA由低電平變為高電平的過程。SCL=1;SDA=0;SDA=1;

STM32CUBEMX I2C 配置教程_#嵌入式_09

2.6  應答信號

數據位的第9位就時應答位。 讀取應答位的流程和讀取數據位是一樣的。示例:   SCL=0;SCL=1;ACK=SDA;       這個ACK就是讀取的應答狀態。

STM32CUBEMX I2C 配置教程_#AT24C02_10

2.7 數據位傳輸時序

通過時序圖瞭解到,SCL處於高電平的時候數據穩定,SCL處於低電平的時候數據不穩定。

那麼對於寫一位數據(STM32--->AT24C02): SCL=0;SDA=data; SCL=1; 

那麼對於讀一位數據(STM32<-----AT24C02): SCL=0;SCL=1;data=SDA;  

STM32CUBEMX I2C 配置教程_#IIC_11

  2.8 總線時序

STM32CUBEMX I2C 配置教程_#IIC_12

四、IIC總線時序代碼、AT24C02讀寫代碼

  在調試IIC模擬時序的時候,可以在淘寶上買一個24M的USB邏輯分析儀,時序出現問題,使用邏輯分析儀一分析就可以快速找到問題。

STM32CUBEMX I2C 配置教程_#單片機_13

 

4.1 iic.c  這是IIC模擬時序完整代碼

#include "iic.h"

/*
函數功能:IIC接口初始化
硬件連接:
SDA:PB7
SCL:PB6
*/
void IIC_Init(void)
{
	RCC->APB2ENR|=1<<3;//PB
	GPIOB->CRL&=0x00FFFFFF;
	GPIOB->CRL|=0x33000000;
	GPIOB->ODR|=0x3<<6;
}

/*
函數功能:IIC總線起始信號
*/
void IIC_Start(void)
{
	IIC_SDA_OUTMODE(); //初始化SDA為輸出模式
	IIC_SDA_OUT=1; 		 //數據線拉高
	IIC_SCL=1;     		 //時鐘線拉高
	DelayUs(4);        //電平保持時間
	IIC_SDA_OUT=0; 		 //數據線拉低
	DelayUs(4);        //電平保持時間
	IIC_SCL=0;     		 //時鐘線拉低
}


/*
函數功能:IIC總線停止信號
*/
void IIC_Stop(void)
{
	IIC_SDA_OUTMODE();    //初始化SDA為輸出模式
	IIC_SDA_OUT=0; 		 //數據線拉低
	IIC_SCL=0;     		 //時鐘線拉低
	DelayUs(4);           //電平保持時間
	IIC_SCL=1;     		 //時鐘線拉高
	DelayUs(4);           //電平保持時間
	IIC_SDA_OUT=1; 		 //數據線拉高
}

/*
函數功能:獲取應答信號
返 回 值:1表示失敗,0表示成功
*/
u8 IIC_GetACK(void)
{
	u8 cnt=0;
	IIC_SDA_INPUTMODE();//初始化SDA為輸入模式
	IIC_SDA_OUT=1; 		  //數據線上拉
	DelayUs(2);         //電平保持時間
	IIC_SCL=0;     		  //時鐘線拉低,告訴從機,主機需要數據
	DelayUs(2);         //電平保持時間,等待從機發送數據
	IIC_SCL=1;     		  //時鐘線拉高,告訴從機,主機現在開始讀取數據
	while(IIC_SDA_IN)   //等待從機應答信號
	{
		cnt++;
		if(cnt>250)return 1;
	}
	IIC_SCL=0;     		  //時鐘線拉低,告訴從機,主機需要數據
	return 0;
}


/*
函數功能:主機向從機發送應答信號
函數形參:0表示應答,1表示非應答
*/
void IIC_SendACK(u8 stat)
{
	IIC_SDA_OUTMODE(); //初始化SDA為輸出模式
	IIC_SCL=0;     		 //時鐘線拉低,告訴從機,主機需要發送數據
	if(stat)IIC_SDA_OUT=1; //數據線拉高,發送非應答信號
	else IIC_SDA_OUT=0; 	 //數據線拉低,發送應答信號
	DelayUs(2);            //電平保持時間,等待時鐘線穩定
	IIC_SCL=1;     		     //時鐘線拉高,告訴從機,主機數據發送完畢
	DelayUs(2);            //電平保持時間,等待從機接收數據
	IIC_SCL=0;     		  	 //時鐘線拉低,告訴從機,主機需要數據
}


/*
函數功能:IIC發送1個字節數據
函數形參:將要發送的數據
*/
void IIC_WriteOneByteData(u8 data)
{
	u8 i;
	IIC_SDA_OUTMODE(); //初始化SDA為輸出模式
	IIC_SCL=0;     		 //時鐘線拉低,告訴從機,主機需要發送數據
	for(i=0;i<8;i++)
	{
		if(data&0x80)IIC_SDA_OUT=1; //數據線拉高,發送1
		else IIC_SDA_OUT=0; 	 //數據線拉低,發送0
		IIC_SCL=1;     		     //時鐘線拉高,告訴從機,主機數據發送完畢
		DelayUs(2);            //電平保持時間,等待從機接收數據
		IIC_SCL=0;     		 		 //時鐘線拉低,告訴從機,主機需要發送數據
		DelayUs(2);            //電平保持時間,等待時鐘線穩定
		data<<=1;              //先發高位
	}
}


/*
函數功能:IIC接收1個字節數據
返 回 值:收到的數據
*/
u8 IIC_ReadOneByteData(void)
{
	u8 i,data;
	IIC_SDA_INPUTMODE();//初始化SDA為輸入模式
	for(i=0;i<8;i++)
	{
		IIC_SCL=0;     		  //時鐘線拉低,告訴從機,主機需要數據
		DelayUs(2);         //電平保持時間,等待從機發送數據
		IIC_SCL=1;     		  //時鐘線拉高,告訴從機,主機現在正在讀取數據
		data<<=1;           
		if(IIC_SDA_IN)data|=0x01;
		DelayUs(2);         //電平保持時間,等待時鐘線穩定
	}
	IIC_SCL=0;     		  		//時鐘線拉低,告訴從機,主機需要數據 (必須拉低,否則將會識別為停止信號)
	return data;
}

4.2 AT24C02.c 這是AT24C02完整的讀寫代碼

#include "at24c02.h"
/*
函數功能:檢查AT24C02是否存在
返 回 值:1表示失敗,0表示成功
*/
u8 At24c02Check(void)
{
	u8 data;
	At24c02WriteOneByteData(255,0xAA);
	data=At24c02ReadOneByteData(255);
	if(data==0xAA)return 0;
	else return 1;
}


/*
函數功能:AT24C02隨機讀數據
函數形參:讀取的地址(0~255)
返 回 值:讀出一個數據
*/
u8 At24c02ReadOneByteData(u32 addr)
{
	u8 data;
	IIC_Start(); //發送起始信號	
	IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //設置寫模式
	IIC_GetACK();//獲取應答
	IIC_WriteOneByteData(addr); //設置讀取數據的位置
	IIC_GetACK();//獲取應答

	IIC_Start(); //發送起始信號	
	IIC_WriteOneByteData(AT24C02_READ_ADDR); //設置讀模式
	IIC_GetACK();//獲取應答
	data=IIC_ReadOneByteData(); //接收數據
	IIC_SendACK(1); //發送非應答信號
	IIC_Stop(); //停止信號
	return data;
}


/*
函數功能:AT24C02寫一個字節的數據
函數形參:
		addr:寫入的地址(0~255)
		data:寫入的數據
*/
void At24c02WriteOneByteData(u32 addr,u8 data)
{
	IIC_Start(); //發送起始信號
	IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //設置寫模式
	IIC_GetACK();//獲取應答
	IIC_WriteOneByteData(addr); //設置寫入數據的位置
	IIC_GetACK();//獲取應答
	IIC_WriteOneByteData(data); //設置寫入的數據
	IIC_GetACK();//獲取應答
	IIC_Stop();  //停止信號
	DelayMs(10); //等待寫入完畢
}


/*
函數 功 能:AT24C02當前位置讀一個字節數據
函數返回值:讀出的數據
*/
u8 At24c02CurrentAddrReadOneByteData(void)
{
	u8 data;
	IIC_Start(); //發送起始信號
	IIC_WriteOneByteData(AT24C02_READ_ADDR); //設置讀模式
	IIC_GetACK();//獲取應答
	data=IIC_ReadOneByteData(); //接收數據
	IIC_SendACK(1); //發送非應答信號
	IIC_Stop(); //停止信號
	return data;
}



/*
函數功能:AT24C02連續讀數據
函數形參:
u8 addr   //讀取的地址(0~255)
u8 len    //讀取的長度
u8 *buff  //讀出的數據存放緩衝區
*/
void At24c02ReadByteData(u32 addr,u8 len,u8 *buff)
{
	u8 i;
	IIC_Start(); //發送起始信號	
	IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //設置寫模式
	IIC_GetACK();//獲取應答
	IIC_WriteOneByteData(addr); //設置讀取數據的位置
	IIC_GetACK();//獲取應答	
	IIC_Start(); //發送起始信號	
	IIC_WriteOneByteData(AT24C02_READ_ADDR); //設置讀模式
	IIC_GetACK();//獲取應答

	for(i=0;i<len;i++)
	{
		 buff[i]=IIC_ReadOneByteData(); //接收數據
		 IIC_SendACK(0); //發送應答信號
	}
	IIC_SendACK(1); //發送非應答信號
	IIC_Stop(); //停止信號
}


/*
函數功能:AT24C02頁寫
函數形參:
		addr:寫入的地址(0~255)
		*data:寫入的數據緩衝區
		len :寫入的長度

1. 頁寫的緩衝區大小是8個字節,一次最多寫8個字節進去。
2. 頁寫的地址是固定的。

0~7 是第一頁
8~15是第二頁			
*/
void At24c02PageWrite(u32 addr,u8 *data,u8 len)
{
	u8 i;
	IIC_Start(); //發送起始信號
	IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //設置寫模式
	IIC_GetACK();//獲取應答
	IIC_WriteOneByteData(addr); //設置寫入數據的位置
	IIC_GetACK();//獲取應答

	for(i=0;i<len;i++)
	{
		IIC_WriteOneByteData(data[i]); //設置寫入的數據
		IIC_GetACK();//獲取應答
	}
	IIC_Stop();  //停止信號
	DelayMs(10); //等待寫入完畢
}


void AT24C02_WriteData(u32 addr,u8 *data,u8 len)
{
    u32 page_remain=8-addr%8; //一頁剩餘的字節數量
    if(page_remain>=len)
    {
        page_remain=len;
    }
    while(1)
    {
        At24c02PageWrite(addr,data,page_remain);
        if(page_remain==len)
        {
            break;
        }
        addr+=page_remain;
        data+=page_remain;
        len-=page_remain;
        if(len>=8)page_remain=8;
        else page_remain=len;
    }
}