梳理STM32F429之通信傳輸部分---NO.6 軟件IIC

目錄

 

一、STM32 的 I2C 特性及架構:

二、軟件IIC的引腳初始化及IIC配置:

三、利用IIC寫EEPROM的讀寫函數:


一、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--);
}

 

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