01-STM8L052R8 I2C讀寫函數實現

1. I2C簡介

 I2C 是很常見的一種總線協議,I2C 是 NXP 公司設計的,I2C 使用兩條線在主控制器和從機之間進行數據通信。一條是 SCL(串行時鐘線),另外一條是 SDA(串行數據線),這兩條數據線需要接上拉電阻,總線空閒的時候 SCL 和 SDA 處於高電平。I2C 總線標準模式下速度可以達到 100Kb/S,快速模式下可以達到 400Kb/S。I2C 總線工作是按照一定的協議來運行的,接下來就看一下 I2C 協議。I2C 是支持多從機的,也就是一個 I2C 控制器下可以掛多個 I2C 從設備,這些不同的 I2C從設備有不同的器件地址,這樣 I2C 主控制器就可以通過 I2C 設備的器件地址訪問指定的 I2C設備了,一個 I2C 總線連接多個 I2C 設備如下圖所示
在這裏插入圖片描述
 圖中 SDA 和 SCL 這兩根線必須要接一個上拉電阻,一般是 4.7K。其餘的 I2C 從器件都掛接到 SDA 和 SCL 這兩根線上,這樣就可以通過 SDA 和 SCL 這兩根線來訪問多個 I2C設備。

2. I2C通信協議

2.1 起始位

  I2C 通信起始標誌,通過這個起始位就可以告訴 I2C 從機,“我”要開始進行 I2C 通信了。在 SCL 爲高電平的時候,SDA 出現下降沿就表示爲起始位,如下圖所示:
在這裏插入圖片描述

2.2 終止位

 停止位就是停止 I2C 通信的標誌位,和起始位的功能相反。在 SCL 爲高電平的時候,SDA出現上升沿就表示爲停止位,如下圖 所示:
在這裏插入圖片描述

2.3 數據傳輸

 I2C 總線在數據傳輸的時候要保證在 SCL 高電平期間,SDA 上的數據穩定,因此 SDA 上的數據變化只能在 SCL 低電平期間發生,如下圖所示:
在這裏插入圖片描述

2.4 應答信號

 當 I2C 主機發送完 8 位數據以後會將 SDA 設置爲輸入狀態,等待 I2C 從機應答,也就是等到 I2C 從機告訴主機它接收到數據了。應答信號是由從機發出的,主機需要提供應答信號所需的時鐘,主機發送完 8 位數據以後緊跟着的一個時鐘信號就是給應答信號使用的。從機通過將 SDA 拉低來表示發出應答信號,表示通信成功,否則表示通信失敗。
在這裏插入圖片描述
 下圖是一幀完整的I2C數據
在這裏插入圖片描述

2.5 寫時序

 I2C寫時序如下圖中右側上面兩小圖所示。每一副小圖中橫線上策表示主機發送的信號或數據,橫線下冊表示從機發送的信號或數據。
 I2C多次寫時序過程如下:
  1. 主機發送起始信號,然後發送七位從機地址和寫標誌位
  2. 從機給一個應答信號
  3. 主機發送八位的寄存器地址
  4. 從機給一個應答信號
  5. 主機發送八位的數據
  6. 從機給一個應答信號
  … 循環執行 5 和 6 兩步 …
  7. 數據發送完成時,主機發送停止信號,結束數據傳輸
在這裏插入圖片描述

2.6 讀時序

 I2C多次讀時序過程如下:
  1. 主機發送起始信號,然後發送七位從機地址和 寫 標誌位
  2. 從機給一個應答信號
  3. 主機發送八位的寄存器地址
  4. 從機給一個應答信號
  5. 主機再次發送起始信號,然後發送七位從機地址和 讀 標誌位
  6. 從機給一個應答信號
  7. 從機發送八位的數據
  8. 主機給一個應答信號
  … 循環執行 7 和 8 兩步 …
  9. 主機不想繼續讀取數據時,從機發送8爲數據後,主機不應答,主機發送停止信號結束傳輸過程
在這裏插入圖片描述

3. STM8L052R8 I2C讀寫示例

3.1 i2c.h

#ifndef _I2C_H_
#define _I2C_H_

#define I2C_INTERFACE		I2C1
#define I2C_CLK				CLK_Peripheral_I2C1

#define I2C_SCL_PORT		GPIOC
#define I2C_SCL_PIN			GPIO_Pin_1

#define I2C_SDA_PORT		GPIOC
#define I2C_SDA_PIN			GPIO_Pin_0

void i2c_init(void);
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len);
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len);

#endif	/* i2c.h */

3.2 i2c_init(初始化)

void i2c_init(void)
{
	I2C_DeInit(I2C_INTERFACE);
	
	GPIO_Init(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_Mode_Out_PP_High_Slow);	// I2C1_SCL -> PC1
	GPIO_Init(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_Mode_Out_PP_High_Slow);	// I2C1_SDA -> PC0
	
	CLK_PeripheralClockConfig(I2C_CLK, ENABLE);		// 使能I2C1時鐘
	
	// 速率 100kHz,7位
	I2C_Init(I2C_INTERFACE, 10000, 0x01, I2C_Mode_I2C, I2C_DutyCycle_2, I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);
	
	I2C_Cmd(I2C_INTERFACE, ENABLE);		// 使能I2C1
}

3.3 發送多個字節

芯片手冊發送流程如下:
在這裏插入圖片描述

/*
* 功   能: i2c主機發送數據
* @param1: 從機地址/從機寄存器地址
* @param2: 要寫入的數據
* @param4: 要寫入數據的長度,單位字節
*/
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len)
{
 	//printf("write test 1\n");
 	delay(100);
 	while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) );	// 等待空閒

   // 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
   I2C_GenerateSTART(I2C_INTERFACE, ENABLE);					
   while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
   //printf("write test 2\n");
   
   // 設置I2C從器件地址,I2C主設備爲寫模式。測試EV6,檢測從器件返回一個應答信號
   I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Transmitter);	
   while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
   //printf("write test 3\n");

   //2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED);	// must add
  
   // 發送數據,檢測EV8_2從機返回一個應答信號
   while(len > 0)
   {
   	I2C_SendData(I2C_INTERFACE, *buf);     
   	while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
   	
   	if(len == 1)		// 只有一個數據
   	{
   		break;		// 跳出循環,發送停止信號
   	}
   	else				// 有多個數據,循環發送
   	{
   		len--;
   		buf++;
   	}
   }
  
  // 發送停止信號
  I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
}

3.4 讀取多個字節

芯片手冊讀取流程如下:
在這裏插入圖片描述
在這裏插入圖片描述

/*
 * 功   能: i2c主機讀取數據
 * @param1: 從機地址
 * @param2: 存放讀取數據的緩存區
 * @param4: 讀取數據的長度,單位字節
 */
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len)
{
#if 0	
	while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) );	// 等待空閒
  
	// 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
	I2C_GenerateSTART(I2C_INTERFACE, ENABLE);					
	while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );	

	// 設置I2C從器件地址,I2C主設備爲寫模式。測試EV6,檢測從器件返回一個應答信號
	I2C_Send7bitAddress(I2C_INTERFACE, addr, I2C_Direction_Transmitter);	
	while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
	
	I2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED);	// must add
	
	// 寄存器地址,檢測EV8_2從機返回一個應答信號
	I2C_SendData(I2C_INTERFACE, *reg_addr);                                                    
	while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
#endif
	
	// 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
	I2C_GenerateSTART(I2C_INTERFACE, ENABLE);					
	while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );	
	//printf("1\n");
	
	// 設置I2C從器件地址,I2C主設備爲讀模式。測試EV6,檢測從器件返回一個應答信號
	I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Receiver);	
	while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) );
	//printf("2\n");
	
	//I2C_ClearFlag(I2C_FLAG_ADDRESSSENTMATCHED);// must add
	
	while(len > 0)
	{
		// 測試EV6
		while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_RECEIVED) );
		*buf = I2C_ReceiveData(I2C_INTERFACE);
		
		//printf("3\n");
		if(len  == 1)	
		{
			// 讀到最後一個字節,無需應答,直接發送停止位
			I2C_AcknowledgeConfig(I2C_INTERFACE, DISABLE);
			I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
		}
		else
		{
			// 不是最後一個字節,發送應答信號,字節數減1,指向下一個存儲空間
			I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
			buf++;			
		}
		len--;
	}
	I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章