STM32(十二)通過I2C總線向EEPROM(AT24C02 )讀寫數據的過程


一、概述

(1)背景

  • I2C(IIC,Inter-Integrated Circuit)總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。
  • 它只需要兩根線即可在連接於總線上的器件之間傳送信息。
  • 主器件用於啟動總線傳送數據,並產生時鍾以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。
  • I2C總線簡化了硬件電路PCB布線,降低了系統成本,提高了系統可靠性。因為I2C芯片(如mpu6050、ft5x06等)除了這兩根線和少量中斷線,與系統再沒有連接的線,用戶常用IC可以很容易形成標准化和模塊化,便於重復利用。

 

                     

(2)傳輸方向

  •   在總線上主和從、發和收的關系不是恆定的,而取決於此時數據傳送方向。
  • 如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送。
  • 如果主機要接收從器件的數據,首先由主器件尋址從器件.然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下,主機負責產生定時時鍾和終止數據傳送。

(3)速度

  • 連接到相同總線上的IC數量只受總線最大電容的限制,串行的8位雙向數據傳輸位速率在標准模式下可達100Kbit/s快速模式下可達400Kbit/s高速模式下可達3.4Mbit/s。
  • 總線具有極低的電流消耗抗高噪聲干擾,增加總線驅動器可以使總線電容擴大10倍,傳輸距離達到15m兼容不同電壓等級的器件工作溫度范圍寬。

(4)地址

  I2C總線上的每一個設備都可以作為主設備或者從設備,而且每一個設備都會對應一個唯一的地址(地址通過物理接地或者拉高,可以從I2C器件的數據手冊得知,如AT24C02芯片,7位地址依次1010xxx, 最低三位可配,如果全部物理接地,則該設備地址為0x50),主從設備之間就通過這個地址來確定與哪個器件進行通信,在通常的應用中,我們把STM32作為主設備,把掛接在總線上的其他設備都作為從設備。

 二、AT240C02 EEPROM介紹

(1)特點

  • 寬范圍的工作電壓1.8V~5.5V低電壓技術
  • 1mA典型工作電流- 1uA典型待機電流·存儲器組織結構
  • 24CO2,256 X8(2K bits)- 24CO4,512×8(4K bits)- 24C08,1024 × 8 (8K bits)-24C16,2048 ×8(16K bits)-24C32,4096 X8(32K bits)- 24C64,8192 × 8(64K bits)
  • 2線串行接口,完全兼容l2C總線
  • I2C時鍾頻率為1 MHz (5V),400 kHz (1.8V,2.5V,2.7V)施密特觸發輸入噪聲抑制
  • 硬件數據寫保護
  • 內部寫周期(最大5 ms)可按字節寫
  • 頁寫:8字節頁(24C02),16字節頁(24CO4/08/16),32字節頁(24C32/64)可按字節,隨機和序列讀
  • 自動遞增地址
  • 高可靠性擦寫壽命:100萬次-數據保持時間:100年

     

 有規格書可知,EEPROM的讀寫速率是100KHZ.

(2)引腳說明

 

 

AT24C02芯片,7位地址依次1010xxx, 最低三位(A0~A2)可配,由原理圖可知三個腳物理接地,地址為1010000,即該設備地址為0x50)。

(3)起始和停止時序

  數據和時鍾線都為高則稱總線處在空閑狀態。當SCL為高電平時SDA的下降沿(高到低叫做起始條件(START,簡寫為S),SDA的上升沿(低到高)則叫做停止條件(STOP,簡寫為P)。參見圖5。

 

(4)數據有效性

     SCL高電平時SDA數據有效,低電平時SDA數據交換,數據變換為高電平或者低電平。

                     

 (5)應答信號:

                        

三、讀寫操作

1、寫操作

  (1).字節寫

  寫操作要求在接收器件地址和ACK應答后,接收8位的字地址。接收到這個地址后EEPROM應答"0",然后是一個8位數據。在接收8位數據后EEPROM應答"0",接着必須由主器件發送停止條件來終止寫序列。
  此時EEPROM進入內部寫周期twR,數據寫入非易失性存儲器中,在此期間所有輸入都無效。直到寫周期完成,EEPROM才會有應答(見圖9)。

               

      (2)頁寫
  • 24C02器件按8字節/頁執行頁寫,24C04/08/16器件按16字節/頁執行頁寫,24C32/64器件按32字節/頁執行頁寫。
  • 頁寫初始化與字節寫相同,只是主器件不會在第一個數據后發送停止條件,而是在EEPROM的ACK以后,接着發送7個(24C02數據。EEPROM收到每個數據后都應答“0”。最后仍需由主器件發送停止條件,終止寫序列(見圖10)。接收到每個數據后,字地址的低3位(24C02)內部自動加1,高位地址位不變,維持在當前頁內。當內部產生的字地址達到該頁邊界地址時,隨后的數據將寫入該頁的頁首。如果超過8個(24C02)數據傳送給了EEPROM,字地址將回轉到該頁的首字節,先前的字節將會被覆蓋。  

            

   MCU向AT24CT02寫數據時,MCU為主機,EEPROM為從機。由上圖可知:

  • 主機通過SDA先發一個起始信號。
  • 發設備地址尋找設備,並設置是寫入還是讀取。
  • 從機回一個Ack應答信號。
  • 主機發送要寫入的EEPROM片內地址。
  • 從機回Ack應答信號。
  • 主機寫數據到從機,每寫一byte,從機回發一個ack應答。
  • 主機發送停止信號,結束數據寫入。
  (3)代碼分析:
#include "iic.h"
/*軟件模擬IIC寫數據到EEPROM*/
GPIO_InitTypeDef GPIO_InitStructure;

#define SDA_OUT   PBout(9)
#define SDA_IN    PBin(9)
#define SCL_OUT   PBout(8)

//初始化SDL、SCL GPIO
void Iic_AT24C02_Init(void)
{
	//1.初始化時鍾
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	
	//2.初始化硬件
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_8;//PB8  PB9 
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;//輸出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
	GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;//速度 快速 25MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);	

	//空閑狀態高電平
	SDA_OUT = 1;
	SCL_OUT = 1;
}

//設置引腳模式
void sda_pin_mode(GPIOMode_TypeDef mode)
{
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;//PB9 
	GPIO_InitStructure.GPIO_Mode  = mode;//模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
	GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;//速度 快速 25MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);	
}


//IIC起始信號
void iic_start(void)
{
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	//保證開始是一個高電平
	SDA_OUT = 1;
	SCL_OUT = 1;
	delay_us(5);     //AT24C02的讀寫速度為100kHZ,一個周期為10us.
	
	SDA_OUT = 0;//時鍾線為高電平期間,數據線由高到低
	delay_us(5);
	
	SCL_OUT = 0;//高速總線上的從機,有通信的設備
	delay_us(5);
}
//發送數據
void iic_send_byte(uint8_t d)// 10101110
{
	int32_t i;
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	SDA_OUT = 0;
	SCL_OUT = 0;
	delay_us(5);
	
	for(i=7;i>=0;i--)
	{
		if(d & (0x1<<i))
			SDA_OUT = 1;
		else
			SDA_OUT = 0;
	
		delay_us(5);
		
		SCL_OUT = 1;
		delay_us(5);//當前數據是可靠的,告訴從機可以讀取數據
		
		SCL_OUT = 0;
		delay_us(5);//當前數據是不可靠的,正在准備
	}	
}
//應答信號
uint8_t iic_wait_ack(void)
{
	uint8_t ack = 0;
	//保證SDA引腳為輸入模式
	sda_pin_mode(GPIO_Mode_IN);
	
	SCL_OUT = 1;
	delay_us(5);//當前數據是可靠的,主機可以訪問
	
	if(SDA_IN == 0)
		ack = 0;//有應答   要
	else
		ack = 1;//無應答   不要了
	
	SCL_OUT = 0;
	delay_us(5);
	
	return ack;
}
void iic_stop(void) { //保證SDA引腳為輸出模式 sda_pin_mode(GPIO_Mode_OUT); SDA_OUT = 0; SCL_OUT = 1; delay_us(5); SDA_OUT = 1;//時鍾線為高電平期間,數據線由低到高 delay_us(5); } int32_t at24c02_write(uint8_t word_addr,uint8_t *buf,uint8_t len) { uint8_t ack = 0; uint8_t *p = buf; //發送起始信號 iic_start(); //設備尋址,設備寫訪問地址為 10100000 = 0xA0 iic_send_byte(0xA0); //等待應答 ack = iic_wait_ack(); if(ack) { printf("devices address failed!\r\n"); return -1; } //告訴從機我要訪問的數據存儲地址 iic_send_byte(word_addr); //等待應答 ack = iic_wait_ack(); if(ack) { printf("word address failed!\r\n"); return -2; } //連續寫數據 while(len--) { //寫入數據 iic_send_byte(*p); //等待應答 ack = iic_wait_ack(); if(ack) { printf("data failed!\r\n"); return -3; } p++; } //發送停止信號 iic_stop(); printf("write success!\r\n"); return 0; }

2、讀操作  

 

讀操作與寫操作初始化相同,只是器件地址中的讀/寫選擇位應為"1"。有三種不同的讀操作方式:當前地址讀隨機讀順序讀

(1)當前地址讀
  • 內部地址計數器保存着上次訪問時最后一個地址加1的值。只要芯片有電,該地址就一直保存。當讀到最后頁的最后字節,地址會回轉到0;當寫到某頁尾的最后一個字節,地址會回轉到該頁的首字節。
  • 接收器件地址(讀/寫選擇位為"1")、EEPROM應答ACK后,當前地址的數據就隨時鍾送出。主器件無需應答"O",但需發送停止條件(見圖12)。

 

                

2.隨機讀

       隨機讀需先寫一個目標字地址,一旦EEPROM接收器件地址和字地址並應答了ACK,主器件就產生一個重復的起始條件。
然后,主器件發送器件地址(讀/寫選擇位為"1"),EEPROM應答ACK,並隨時鍾送出數據。主器件無需應答"O",但需發送停止條件(見圖13)。

 

 

 

  • 主機通過SDA先發一個起始信號。
  • 發設備地址尋找設備,並設置是寫入(因為要寫入讀取數據的地址)。
  • 寫入要讀取的地址
  • 從機回一個Ack應答信號。
  • 主機再發一個起始信號開始讀
  • 主機發送要寫入的EEPROM片內地址(讀寫位設置為讀)。
  • 讀數據。
  • 主機發送停止信號,結束數據讀取。
3.順序讀

  順序讀可以通過“當前地址讀"或“隨機讀"啟動。主器件接收到一個數據后,應答ACK。只要EEPROM接收到ACK,將自動增加字地址並繼續隨時鍾發送后面的數據。若達到存儲器地址末尾,地址自動回轉到0,仍可繼續順序讀取數據。
主器件不應答"0",而發送停止條件,即可結束順序讀操作(見圖14)。

 

 

 

 

 

 

 

#include "iic.h"

GPIO_InitTypeDef GPIO_InitStructure;
void Iic_AT24C02_Init(void)
{
	//1.初始化時鍾
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	
	//2.初始化硬件
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_8;//PB8  PB9 
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;//輸出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
	GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;//速度 快速 25MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);	

	//空閑狀態高電平
	SDA_OUT = 1;
	SCL_OUT = 1;
}

void sda_pin_mode(GPIOMode_TypeDef mode)
{
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;//PB9 
	GPIO_InitStructure.GPIO_Mode  = mode;//模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
	GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;//速度 快速 25MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);	
}

void iic_start(void)
{
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	//保證開始是一個高電平
	SDA_OUT = 1;
	SCL_OUT = 1;
	delay_us(5);
	
	SDA_OUT = 0;//時鍾線為高電平期間,數據線由高到低
	delay_us(5);
	
	SCL_OUT = 0;//高速總線上的從機,有通信的設備
	delay_us(5);
}

void iic_send_byte(uint8_t d)// 10101110
{
	int32_t i;
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	SDA_OUT = 0;
	SCL_OUT = 0;
	delay_us(5);
	
	for(i=7;i>=0;i--)
	{
		if(d & (0x1<<i))
			SDA_OUT = 1;
		else
			SDA_OUT = 0;
	
		delay_us(5);
		
		SCL_OUT = 1;
		delay_us(5);//當前數據是可靠的,告訴從機可以讀取數據
		
		SCL_OUT = 0;
		delay_us(5);//當前數據是不可靠的,正在准備
	}	
}
uint8_t iic_recv_byte(void)
{
	int8_t i;
	uint8_t data;
	//保證SDA引腳為輸入模式
	sda_pin_mode(GPIO_Mode_IN);
	
	//當前數據不可靠,切換數據
	SCL_OUT = 0;
	delay_us(5);
	
	for(i=7;i>=0;i--)//MSB  10101111
	{
		SCL_OUT = 1;
		delay_us(5);//當前數據可靠,讀把
		
		if(SDA_IN == 1)
		{
			data |= (0x1<<i);
		}
		else
		{
			data &= ~(0x1<<i);
		}
		
		SCL_OUT = 0;
		delay_us(5);//當前數據不可靠,切換數據
	}
	
	return data;
}

void iic_ack(uint8_t ack)
{
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	SDA_OUT = 0;
	SCL_OUT = 0;
	delay_us(5);//當前數據不可靠
	
	//發送高/低電平
	SDA_OUT = ack;
	delay_us(5);//准備數據
	
	SCL_OUT = 1;
	delay_us(5);//當前數據可靠,然后從機可以訪問
	
	SCL_OUT = 0;
	delay_us(5);//當前數據bu可靠,切換數據
}

uint8_t iic_wait_ack(void)
{
	uint8_t ack = 0;
	//保證SDA引腳為輸入模式
	sda_pin_mode(GPIO_Mode_IN);
	
	SCL_OUT = 1;
	delay_us(5);//當前數據是可靠的,主機可以訪問
	
	if(SDA_IN == 0)
		ack = 0;//有應答   要
	else
		ack = 1;//無應答   不要了
	
	SCL_OUT = 0;
	delay_us(5);
	
	return ack;
}
void iic_stop(void)
{
	//保證SDA引腳為輸出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	SDA_OUT = 0;
	SCL_OUT = 1;
	delay_us(5);
	
	SDA_OUT = 1;//時鍾線為高電平期間,數據線由低到高
	delay_us(5);

}
int32_t at24c02_write(uint8_t word_addr,uint8_t *buf,uint8_t len)
{
	uint8_t ack = 0;
	uint8_t *p = buf;
	//發送起始信號
	iic_start();
	
	//設備尋址,設備寫訪問地址為 10100000 = 0xA0
	iic_send_byte(0xA0);
	
	//等待應答
	ack = iic_wait_ack();
	if(ack)
	{
		printf("devices address failed!\r\n");
		return -1;
	}
	
	//告訴從機我要訪問的數據存儲地址
	iic_send_byte(word_addr);
	//等待應答
	ack = iic_wait_ack();
	if(ack)
	{
		printf("word address failed!\r\n");
		return -2;
	}
	
	//連續寫數據
	while(len--)
	{
		//寫入數據
		iic_send_byte(*p);
		
		//等待應答
		ack = iic_wait_ack();
		if(ack)
		{
			printf("data failed!\r\n");
			return -3;
		}
		p++;
	}
	
	//發送停止信號
	iic_stop();
	
	printf("write success!\r\n");
	
	return 0;
}

int32_t at24c02_read(uint8_t word_addr,uint8_t *buf,uint8_t len)
{
	uint8_t ack = 0;
	uint8_t *p = buf;
	//發送開始信號
	iic_start();
	
	//設備尋址,設備寫訪問地址  0xA0
	iic_send_byte(0xA0);
	
	//等待應答
	ack = iic_wait_ack();
	if(ack) //1 無應答   0 應答
	{
		printf("devices address failed!\r\n");
		return -1;
	}
	
	//告訴從機我要訪問的數據存儲地址
	iic_send_byte(word_addr);
	//等待應答
	ack = iic_wait_ack();
	if(ack) //1 無應答   0 應答
	{
		printf("word address failed!\r\n");
		return -2;
	}

	//再次發送開始信號
	iic_start();
	
	//設備尋址,設備讀訪問地址  0xA1
	iic_send_byte(0xA1);
	
	//等待應答
	ack = iic_wait_ack();
	if(ack) //1 無應答   0 應答
	{
		printf("devices address failed!\r\n");
		return -3;
	}
	//連續接收數據
	len = len - 1;
	while(len--)
	{
		*p++ = iic_recv_byte();
		//發送應答
		iic_ack(0);
	}
	
	//接收最后一個字節
	*p = iic_recv_byte();
	
	//發送不應答
	iic_ack(1);
	
	//發送停止信號
	iic_stop();
	
	printf("read success!\r\n");
	
	return 0;	
}

  

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM